Книга
Книга
От редактора перевода
Подробное содержание предлагаемой читателю книги освещено в предисловии авторов и хорошо просматривается из оглавления, поэтому мы не будем останавливаться на нем, а сделаем несколько замечаний об эволюции языков программирования, обусловленной потребностями вычислительной математики.
С полной уверенностью можно сказать, что данная книга явилась результатом долгой, жесткой, порой драматической конкуренции различных разновидностей языков программирования, применяемых в научных и инженерных вычислениях. Несколько последних десятилетий одним из любимых вопросов специалистов по вычислительной математике оставался вопрос об основном языке программирования, используемом в этой области. В течение долгих десятилетий несомненным лидером оставался Фортран с его разновидностями Фортран-4, Фортран-77, Фортран-90 и т. д. Многолетний груз созданного на этих языках программного обеспечения и разнообразных математических библиотек сделал почти невозможной замену данного инструмента. С другой стороны, развитие этого языка в сторону матричных вычислений укрепляло его позиции в различных областях прикладной математики.
Активное развитие Фортрана таило в себе и подводные камни. В сущности их было два. Первый — это снижение стандартизированности языка программирования. На отдельных платформах стали развиваться мощные разновидности языка Фортран, но написанное на них программное обеспечение не было легко переносимым на другие платформы. Вторым подводным камнем стала устаревшая идеология языка Фортран. Попытки ее замены упирались в первую из указанных проблем.
Казалось, что ситуацию не смогло переломить появление изящного многоцелевого языка программирования С. Будучи созданным для системных целей, этот язык стал быстро набирать очки в изначально свойственной дня него области ——в области прикладных вычислений. Меньшая приспособленность для решения математических задач искупалась в этом языке его несравнимо большей изящностью, что чисто субъективно не могли не оценить конкретные пользователи. Оказалось, что проблема, связанная с багажом математических библиотек относительно легко разрешима, так как появились программы- ретрансляторы программ, написанных на разновидностях языка Фортран, в программы на языке С.
Коренной перелом произошел с момента появления новой модификации языка С — языка C++. Чисто формально язык C++ является некоторой надстройкой над языком С. Фактически же на свет появился новый мощный объектно-ориентированный язык, совмещающий в себе при грамотном программировании высокую скорость выполнения кода и всю широту возможностей объектно-ориентированного языка: возможность создания новых типов,
66От редактора перевода
6
6
определение математических операций над этими типами, инкапсуляцию и наследование, переопределение уже существующих операций и функций и т. д.
Изначально язык программирования C++ не содержит специальных средств, ориентированных на решение задач, связанных с компьютерной алгеброй, но создание специальных библиотек классов с блеском решает эту проблему. При этом вопрос о конкретной реализации этой библиотеки уходит на второй план по сравнению с вопросом о том, как ее реализовать. При грамотном описании всех свойств библиотеки сама ее реализация занимает относительно немного времени и в данной книге не рассматривается. Основной упор делается на содержательную сторону построения подобной библиотеки, т. е. на описание классов библиотеки, функций, объектов этих классов, операций, доступных над классами.
В книге дается развернутое описание основных понятий компьютерной алгебры и взаимосвязей между ними. Язык C++ является далеко не единственным языком, на котором можно решать проблемы компьютерной алгебры, и в книге приводится краткий обзор использования в этой области других специализированных языков.
Следует еще раз отметить, что все вводимые в книге понятия оцениваются именно с точки зрения их применимости к решению задач компьютерной алгебры, что делает эту книгу интересной для математиков и математиков- прикладников, имеющих дело с подобными вопросами. Мы надеемся, что эта книга будет весьма интересной широкому кругу российских читателей.
Г. М. Кобельков
Предисловие
В этой книге мы показываем, как объектно-ориентированное программирование может использоваться дая реализации системы символьной алгебры и как зта система применяется к различным областям математики и физики.
В самом узком значении компьютерная алгебра используется для обработки научных и инженерных формул. Обычно математическая формула с помощью языков программирования (например, С, Фортран и Pascal) может быть только вычислена и каждой переменной присваивается соответствующее значение. Однако та же формула может рассматриваться как математический объект в системе символьной алгебры, который допускает формальные преобразования, такие, как, например, дифференцирование, интегрирование и разложение в ряд, на основе численных манипуляций. Следовательно, такой подход крайне полезен в исследовательских и научных вычислениях.
Объектно-ориентированное программирование открыло новую эру для программирования в информатике, где оно появилось как возможное средство при разработке программного обеспечения. В целом, объектно-ориентированное программирование является важным подходом в анализе задач; разработке систем и построении решений. С эффективным использованием этой методологии программные продукты становятся в большей степени избавленными от ошибок, более легкими в сопровождении, более часто используемыми и расширяемыми.
Целью этой книги является демонстрация того, как особенности объектноориентированного программирования могут быть применены к разработке системы компьютерной алгебры. Среди многих объектно-ориентированных языков программирования, доступных в настоящее время, мы выбрали C++ в качестве нашего языка программ ирования. Это наиболее широко используемый язык объектно-ориентированного программирования, который успешно применяется многими программистами в различных прикладных областях. Разработка основана частично на признанных принципах и частично на большом опыте и обратной связи ее практического использования. Многие опытные программисты и программистские коллективы в промышленности и академической среде используют C++. Помимо причин, указанных выше, мы предпочли C++ другим объектно-ориентированным языкам из-за его эффективности в скорости выполнения и использовании указателей и шаблонов.
В главе 1 вводится понятие компьютерной алгебры. В ней мы обсуждаем важнейшие свойства и требования системы компьютерной алгебры, приводим справочный материал с указанием некоторых опасностей и ограничений. Наконец, мы представляем систему компьютерной алгебры — SymbolicC++. Эта новая система имеет много преимуществ перед существующими системами компьютерной алгебры. Кратко суммируются характеристики и функциональные возможности системы.
В главе 2 представлены общематематические объекты для системы компьютерной алгебры. Мы описываем, как строятся базовые математические структуры для формирования более сложных математических структур.
В главе 3 дается краткое описание некоторых систем компьютерной алгебры, доступных на рынке, например, REDUCE, MAPLE, AXIOM, МАТНЕ- 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 (параллельная виртуальная машина) может быть использована с
абстрактными типами данных. В главе 12 мы описываем некоторую технику обработки ошибок. Мы вводим понятие обработки исключительной ситуации. В демонстрационных целях приводятся некоторые примеры. Наконец, в гл. 13 мед показываем, как Gnuplot и PostScript могут использоваться для рисования фигур.
Уровень представления предмета в данной книге таков, что ее можно изучать, еще не получив специального научного образования. Мы соблюдаем баланс между практическим программированием и лежащим в его основе языком. Книга идеально подходит для использования в курсе по символьным вычислениям и объектно-ориентированному программированию. Начинающий программист также извлечет пользу из книги.
В списке литературы приведены учебные издания, полезные при изучении языка C++ [6], [13], [23], [29], [31], [37], [45], [55]. По структурам данных мы отсылаем читателей к Budd (1994) [9], а в области научных приложений — к работам Steeb et al. (1993) [48], Steeb (1994) [49] и Steeb (1999) [52].
Приведенные в книге C++ программы протестированы со всеми последними компиляторами C++, которые поддерживают стандартный C++ и включают реализацию стандартной библиотеки шаблонов.
Несомненно, эта книга может быть расширена. Если у вас есть замечания или предложения, мы были бы рады получить их. Электронные адреса авторов:
Willi-Hans Steeb:Tan Kiat Shi:Yorick Hardy:Willi-Hans Steeb:Tan Kiat Shi:Yorick Hardy:whsOna.rau.ac.za steeb_wb@yahoo.com ktanOilog.co.jp yorickhardyOyeihoo. com
Willi-Hans Steeb:
Tan Kiat Shi:
Yorick Hardy:
Willi-Hans Steeb:
Tan Kiat Shi:
Yorick Hardy:
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]) называется технология символьных преобразований математических формул цифровыми компьютерами. Например, такое выражение, как
z - 2 * z- а),,
ах
в результате символьных вычислений приводится к виду
z — 2 * а.
Символьные упрощения алгебраических выражений составляют основу компьютерной алгебры. Символьное дифференцирование, использующее правила вычисления производной от суммы, произведения и частного, также должно включаться в любую систему компьютерной алгебры. Кроме того, такие выражения, как sin2(3;) + cos2(ж) и ch2(ж) — sh2(3;), должны заменяться их численным значением — числом 1. Еще один важный аспект любой системы компьютерной алгебры состоит в том, что она позволяет пользователю определять правила. Примерами служит реализация внешнего произведения и скобки Ли. Значительную часть систем компьютерной алгебры составляют также символьные преобразования многочленов, например при вычислении наибольшего общего делителя двух многочленов или нахождении коэффициентов многочлена.
Долгое время эту дисциплину называли по-разному: то символьными и алгебраическими вычислениями, то символьными и алгебраическими преобразованиями и, наконец, остановились на названии «компьютерная алгебра». Программы алгебраических вычислений уже давно применяются во многих областях науки и техники. Наиболее широко они используются в таких приложениях, в которых алгебраические вычисления крайне громоздки и трудоемки, скажем в общей теории относительности, небесной механике и квантовой хромодинамике. Одним из первых применений таких программ было вычисление кривизны поля заданного метрического тензора. При этом в основном использовалось символьное дифференцирование.
L2・ Свойства систем компьютерной алгебры
Что должна уметь делать система компьютерной алгебры? Прежде всего, она должна уметь работать с такими типами данных, как очень длинные целые числа, рациональные числа, комплексные числа, кватернионы и т. п. Основу символьной части системы должно составлять упрощение выражений, например:
а + 0 = а,
0 + а = а,
а — а = 0,
—а + а = 0,
а * 0 = 0,
0 * а = 0,
а * 1 = а,
1 * а —— а,
а° =
1.
В большинстве систем символы считаются перестановочными, т. е.
a* b = b * а.
Так, выражение
(а + Ь) * (а — Ь)
должно преобразовываться к виду
а а — b * Ь.
Если символы перестановочны, то это должно указываться специальной командой. Кроме того, система компьютерной алгебры должна выполнять упрощения тригонометрических и гиперболических функций, например,
sin(O) = 0, cos(O) = 1,ch(0) = 1, sh(O) = 0.
Выражение ехр(0) должно упрощаться до 1, a In 1 — до 0. Такие выражения, как
sin2 (ж) + cos2 (ж), ch2 (ж) — sh2(a;),
должны упрощаться до 1. Кроме символьного дифференцирования и символьного интегрирования любая система компьютерной алгебры должна также осуществлять символьные вычисления с векторами, матрицами и массивами. Так, скалярное произведение и векторное произведение должны вычисляться в символьной форме. Для квадратных матриц символьно должны вычисляться след и детерминант. Кроме того, такая система должна уметь обращаться с числами. Поэтому она должна располагать возможностью переходить от символьных вычислений к численным и обратно. Далее, система компьютерной алгебры должна быть языком программирования. Например, она должна содержать условные переходы и циклы, а также допускать функции и процедуры.
Недостатки систем компьютерной алгебры
Хотя системы компьютерной алгебры существуют уже много лет, им все еще свойственны ошибки и ограничения. Ниже перечислен ряд типичных для таких систем дефектов.
Характерный пример ——вычисление выражения
,а? +必 _ 2аЬ.
Некоторые системы компьютерной алгебры показывают, что результатом слу- ясит a — Ь. Ясно, что на самом деле результат равен ±|а — b\. В качестве еще одного примера рассмотрим ранг матрицы
ранг матрицы ——это число линейно независимых столбцов (которое равно числу линейно независимых строк). Если £ = 0, то ранг этой матрицы равен 0. С другой стороны, если 上 声 0, то ее ранг равен 1. Таким образом, системы компьютерной алгебры сталкиваются с неопределенностью при вычислении ранга этой матрицы. Аналогичная проблема возникает при нахождении матрицы, обратной к матрице
7匸
\ ж о
Обратная матрица существует, лишь если z 次 0.
Другая проблема возникнет, если предложить системам компьютерной алгебры взять интеграл
xndx^
где п — целое число. Если тг 声一1, этот интеграл дается формулой
Если же тг = —1, то получим 1п(ж).
Еще одна неопределенность возникает при вычислении 0°. Рассматривая /(ж) = z。三 exp(zln(£)) при ж > 0 и затем применяя правило Лопиталя, находим 0° = 1 в качестве возможного определения 0°. Многие системы компьютерной алгебры сталкиваются с трудностями, вычисляя значение выражения
х
х + sin(z)
при z = 0 по правилу Лопиталя. Правильный результат равен 1/2. Следует также иметь в виду, что при решении уравнения
а * с = 0
система компьютерной алгебры должна различать случаи а — 0 и ж = 0.
Многие недостатки описываемых систем обнаруживаются при обращении с комплексными числами и точками ветвления функций в комплексном анализе. Комплексные числа и функции должны удовлетворять критерию Аслаксе- на [2]. Так, выражение
ехр(1п(г))
должно упрощаться до %, но выражение
1п(ехр(г))
для комплексных чисел упрощаться не должно. Нужно обращать внимание на разрезы комплексной плоскости при рассмотрении многозначных комплексных функций. Большинство систем компьютерной алгебры по умолчанию считают аргумент вещественным.
Более подробный обзор недостатков систем компьютерной алгебры можно найти в статье [52].
Разработка системы компьютерной алгебры
Большинство систем компьютерной алгебры основаны на языке Lisp. Название этого языка программирования — аббревиатура английских слов List processing — обработка списков. Основное назначение языка Lisp — манипуляции со структурами данных, называемыми списками, которые заключаются в скобки. Многие мощные системы компьютерной алгебры основаны на этом языке, например REDUCE, MACSYMA, DERIVE, AXIOM и MuPAD. Структура системы AXIOM основана на объектно-ориентированном программировании в среде языка Lisp. Системы компьютерной алгебры MAPLE и MATHEMATICA основаны на языке С. Все вышеперечисленые системы — это мощные программные продукты, способные совершать символьные вычисления. Однако эти программные системы независимы, и перенос выражений из одной из них в другую программную среду вроде С — задача довольно утомительная, времяёмкая и подверженная ошибкам. Поэтому было бы полезно наделить какой-нибудь язык высокого уровня способностью обрабатывать символьные выражения. С другой стороны, объектно-ориентированные языки программирования обладают всеми необходимыми средствами, чтобы выполнить эту работу красиво.
Здесь мы покажем, что объектно-ориентированное программирование с использованием языка C++ может применяться для разработки системы компьютерной алгебры. Объектно-ориентированное программирование — это методология построения программных систем, основанная не на процедурах, а на классах. Этот подход в наибольшей степени реализует такие качества программ, как модульность и скрытие информации. Объектно-ориентированная структура обладает множеством достоинств. Например} она объединяет данные и функции, обрабатывающие эти данные, в единый модуль. Такой модуль (абстрактный тип данных) называется классом.
Мы выбираем C++ в качестве объектно-ориентированного языка программирования по следующим причинам: C++ позволяет ввести абстрактные типы данных. Поэтому мы можем ввести типы данных, используемые в системе компьютерной алгебры, как абстрактные типы данных. Язык C++ поддерживает основные понятия объектно-ориентированного программирования: инкапсуляцию, наследование, полиморфизм (включая динамическое связывание переменных) и перегрузку операций. Он обеспечивает хорошую поддержку управления динамической памятью и допускает как процедурное, так
объектно-ориентированное программирование. Менее абстрактная форма полиморфизма обеспечивается через поддержку шаблонов. Мы перегружаем операции
для наших абстрактных типов данных, таких, как сверхдлинные целые числа, рациональные числа, комплексные числа или символьные типы данных. Клас- СЬ1 векторов и матриц реализуются на основе шаблонов, чтобы их можно было использовать с другими абстрактными типами данных.
Другое преимущество этого подхода состоит в том, что поскольку сама система символьных вычислений написана на C++, то ее легко расширить и приспособить к конкретной решаемой задаче. Классы (абстрактные типы данных) включены в заголовочный файл и могут быть использованы в любой программе на C++ посредством команды #include "ADT.h" в начале программы.
Для реализации этой концепции нужно применить следующие средства языка C++:
понятие класса
перегрузка операций и функций
наследование классов
виртуальные функции
шаблоны функций
шаблоны классов
⑺ библиотека стандартных шаблонов
Развитая система SymbolicCH1- включает следующие абстрактные типы данных (классы):
Verylong Rational Complex Quaternion Derive
сверхдлинные целые числа.
шаблон-класс для рациональных чисел.
шаблон-класс для комплексных чисел.
шаблон-класс для кватернионов.
шаблон-класс для точного дифференцирования.
Vector/Matrix шаблоны-классы для векторов и матриц.
Array String Bitvector List Set Polynomial Sum
шаблоны-классы для массивов размерности до четырёх.
строки.
бит-векторы.
связанные списки.
конечные множества.
шаблон-класс для многочленов.
шаблон-класс для символьных вычислений, включающий такие правила, как упрощение, дифференцирование, коммута
Type/Pair
тивность и некоммутативность.
атом и связанная пара для упрощенной Lisp-системы.
Система включает подходящие преобразования типов между абстрактными типами данных, а также между абстрактными типами данных и основными типами данных.
Преимущества этой системы следующие:
Объектно-ориентированная структура и адекватный подход к основным и абстрактным типам данных.
Система независима от используемой операционной системы, т. е. для любых операционных систем существуют мощные компиляторы с C++.
Пользователю предоставляются исходные тексты.
Легко добавляются новые классы (абстрактные типы данных).
Учтен стандарт языка C++.
Пользователю достаточно изучить C++, чтобы применять систему компьютерной алгебры.
Легко добавить ассемблерный код для прогонки на конкретном процессоре.
Методы и операторы преобразования типов обеспечивают алфавитноцифровой интерфейс.
Классы (абстрактные типы данных) включаются в заголовочный файл и могут быть сделаны доступными любой программе на C++ посредством команды #include "ADT.h" в начале программы.
Классы могут быть связаны с параллельной виртуальной машиной (ПВМ).
Можно использовать библиотеку стандартных шаблонов.
Глава 2
Математические основы компьютерной алгебры
Кольца и поля
Типы данных системы компьютерной алгебры включают в себя целые числа, рациональные числа, вещественные числа, комплексные числа, кватернионы и символы. С другой стороны, основные типы данных в таких языках программирования? как С и C++, суть целые числа, символы, числа с плавающей точкой (с обычной и двойной точностью) и указатели. Из этих основных типов данных мы можем образовывать массивы, например массивы целых чисел или массивы символов (строки). Математическими структурами этих типов данных (кроме указателей) служат кольца и поля [22], [3].
Кольцо —это упорядоченная тройка (К, +, *), состоящая из множества R и двух определенных на нем бинарных операций + (сложение) и * (умножение), удовлетворяющих следующим условиям:
пара (冗,+) является коммутативной группой;
умножение ассоциативно;
оно допускает тождественный (или единичный) элемент, обозначаемый символом I;
умножение дистрибутивно (с обеих сторон) относительно сложения, т. е.
X * {у + z) = X*y+X*ZH {x + y)*z—x*z + y*zдля всех x、y〉z € R.
Обычно в аксиомы кольца не включают условие (с)——существование единичного элемента относительно умножения. Тогда различают кольца вообще и кольца с единицей. Те же различия применимы к приводимым ниже определениям подкольца и области целостности. Если к тому же умножение коммутативно, то 兄 называют коммутативным кольцом.
Подкольцом кольца R называется его подмножество S, удовлетворяющее следующим условиям:
S является подгрупой аддитивной группы R;
если х Е S и у Е то х * у Е
(c)」€S.
Таким образом, подкольцо является кольцом.
Если элемент a Е R имеет обратный элемент относительно умножения, т. е. существует (единственный) элемент а~' е 五,такой, что
a * a一1 = a-1 * a = I,
то этот элемент а называется обратимым элементом кольца R.
Множество обратимых элементов кольца R обозначается К*. Если каждый ненулевой элемент кольца R обратим, то R называется кольцом с делением. Коммутативное кольцо с делением называется полем. S является подполем поля F, если S является подкольцом поля F и из условий z е S, z * 0 следует, что х"1 € S.
Кольцо R является упорядоченным, если существует непустое подмножество Р и R、называемое множеством положительных элементов R, которое удовлетворяет следующим условиям:
если аЕРибеР, тоа + 6еРиа*ЬеР;
для любого а £ R верно в точности одно из следующих утверждений:
а € Р, а = 0, (—а) € Р.
Коммутативное кольцо К называется областью целостности^ если из условий хЕК,уЕКих*у = О следует, что или ж = 0, или у = 0. Если х*у = 0? х ^ О иу#0, то%иу называются делителями нуля в К.
Характеристика кольца определяется как аддитивный порядок его (мультипликативного) тождественного элемента, т. е. R обладает характеристикой т, если m есть наименьшее целое положитель ное число, для которого т*1 = 0. Кольцо имеет характеристику 0 (или, при другом определении, ос), если никакое кратное единичного элемента не равно нулю.
Примерами колец служат (Z, +, *) и (Q, +, *). Z является подкольцом кольца Q, но можно показать, что у Z нет собственных подколец. Единственными обратимыми элементами Z служат 1 и —1, тогда как в Q все ненулевые элементы обратимы. Поскольку Q ——коммутативное кольцо, отсюда следует, что Q является полем. Q — подполе поля R. Z, Q и R можно упорядочить символом <,понимаемым в обычном смысле. Все эти кольца являются областями целостности и имеют характеристику 0.
Двусторонний идеал I кольца R ——это непустое подмножество К, удовлетворяющее следующим аксиомам:
(а) I является подгруппой аддитивной группы 冗, (b) для всех ж 6 7 и всех а,Ь 6 R выполняется
(i) а*х е R и (ii) x^b R.
/ является левым идеалом, если это множество удовлетворяет аксиомам (а) и (b) (i), и правым идеалом, если оно удовлетворяет аксиомам (а) и (b) (ii). Если кольцо R коммутативно, все его идеалы двусторонние.
Идеал / R К коммутативного кольца К называется максимальным идеалом этого кольца, если для любого его идеала М, удовлетворяющего условию / G G К, либо М = I, либо М — К. Для двух данных идеалов / и J коммутативного кольца К определим их сумму / 十 J как множество всех элементов
,а их произведение IJ как множество записаны в виде,а их произведение IJ как множество записаны в видеI,... ,п. I + J и IJ также являютсяI,... ,п. I + J и IJ также являются
,а их произведение IJ как множество записаны в виде
,а их произведение IJ как множество записаны в виде
I,... ,п. I + J и IJ также являются
I,... ,п. I + J и IJ также являются
кольца К вида х + у, где х G I и у Е J всех элементов К, которые могут быть
^1 * У1 + Ж2 * У2 Нh Жп * уП1
где n е N, g е / Н 该 е J ДЛЯ всех i
{ж * Ь\х £ К}, где Ь — фиксирован-{ж * Ь\х £ К}, где Ь — фиксирован-идеалами.
{ж * Ь\х £ К}, где Ь — фиксирован-
{ж * Ь\х £ К}, где Ь — фиксирован-
Если К — коммутативное кольцо и / —
ный элемент К, то I является идеалом, называемым главным идеалом. Тогда говорят, что Ь порождает 7. Область целостности, все идеалы которой являются главными, называется кольцом главных идеалов. Если / есть идеал из
кольца главных идеалов D и порождается элементами з;1,... ,жп, т. е. I есть
множество всех элементов вида
% *2] + U2 * Х2 + ■ - + Un * Хп,
где u^D, то I порождается некоторым единственным элементом d. Этот элемент 2 называется наибольшим общим делителем (НОД) элементов xi^... ,жп. Элементы xiy.. ^хп называются взаимно простыми, если их НОД есть 1.
Пересечение п идеалов, порожденных элементами 七】,忆必 взятыми по одному, также является идеалом и поэтому будет порождаться неким единственным элементом т. Этот элемент т называется наименьшим общим кратным (НОК) элементов х^,... ,хп. Элемент р кольца главных идеалов D называется простым или неприводимым, если он не является обратимым элементом D и если из р = а * й, где a.)b £ D、следует, что либо а, либо b — обратимый элемент.
Область целостности D является областью однозначности разложения на множители, если
для всех а Е D\ {0} а либо является обратимым элементом, либо представим как произведение конечного числа неприводимых элементов области D и
упомянутое в (а) разложение единственно с точностью до упорядочения неприводимых элементов и подстановки ассоциированных элементов.
Пример. Множества
-^2 — {* • • , —4, —2,0,2,4,...} и 7з = {... , —6, — 3,0,3,6,...} являются максимальными идеалами кольца Z.
/12 — {•••)—24, —12,0,12,24,...} и 7g ~ {• • • )一16, —8,0,8,16, • • •} являются идеалами, но не максимальными идеалами.
2*2. Целые числа
В этом разделе мы введем целые числа. Доказательства приведенных ниже теорем можно найти в [3], [21]. Пусть N —это множество натуральных чисел
N := {0,1,2?...}.
Множество целых чисел
Z = {.…,一3, —2, —1,0,1,2,3,}
можно построить из N следующим образом. Определим отношение эквивалентности 石 на N х N формулой
(а, у^Е^х^у') <=> х + у' = х' + у.
Мы хотим, чтобы формула £ — у — х1 — уг выполнялась, как только будет определена операция «минус». Множество Z тогда определяется как Nx N/石. Для любых элементов z и z‘ кольца Z, таких, что
z = РЕ0П), z' = РеЗ,П‘),
где ре обозначает каноническое отображение из N х N на Z, определим сумму и произведение z и z‘ формулами
z + 〃 = рн(£ + "у + J), z * zf = ре{^ * х1 + у * уг^х * уг + х * у1}.
Такое определение выбрано потому, что мы хотим, чтобы выполнялись формулы
(х-у) + 3 - J) = 3 + 疗)-(у + у') И
(X — у) * (з/ ~ уГ) =(X * X + у * J) _(£*/+£,* у).
С этими двумя операциями Z является коммутативным кольцом. Отображение п —> ре(п, 0) является инъективным отображением N в Z, сохраняющим сложение и умножение. Мы можем, следовательно, отождествить N с некоторым подмножеством кольца Z. Если определить — z как элемент, обратный к z относительно сложения, то можно показать, что либо г G N, либо —г € N. Если z £ N\{0}, будем называть это z положительным целым числом; если же —z е N\{0}, будем называть это z отрицательным целым числом. Заметим, что а ~ b — а + (—6), если а,Ь R N. Целое число а 声 0 называется делителем целого числа 6 (пишется а|6), если существует целое число с, такое, что b = а*с. Если а\Ь, мы будем называть это b кратным а. Чтобы показать необходимость ограничения а 声 0, предположим, что 0|Ь. Если 6 ^ 0, то для некоторого с е Z должно выполняться b = 0 * с, что невозможно; если же b = 0, мы получим 0 = 0 * с, что верно для любого с € Z. Если 6, с, ж, у € Z, то целое число Ь * х + с* у называется линейной комбинацией бис. Мы получили следующее утверждение.
Теорема. Если а\Ь и а|с; то а\(Ь^ х + с^у) для всех z,у G Z.
Целое число 力 R 0,±1 называется простым тогда и только тогда, когда у него нет иных делителей, кроме 土1 и р. Ясно, что число —р является простым тогда и только тогда, когда р есть простое число. В дальнейшем нас будут интересовать в основном положительные простые числа. Число положительных простых чисел бесконечно. Если а = b * с, где |Ь| > 1 и |с| > 1, то целое число а называется составным. Поэтому каждое целое а 声 0, 土1 является либо простым, либо составным. Если а\Ь и а|с, то а называется общим делителем чисел b и с. Если, кроме того, каждый общий делитель чисел бис является также делителем числа а, то число а называется наибольшим общим делителем чисел бис. Пусть с и с? суть два наибольших общих делителя чисел а 声 0 и b R 0. Тогда c\d и d|c; значит, числа end различаются только знаком.
ничимся лишь положительным наибольшим общим делителем двух целых и^л о и й, который будем обозначать либо d, либо (а, Ь). Таким образом, d и самом деле является наибольшим целым числом, которое делит и а, и Ь.
“ Мы предположили, что (а) у каждой пары ненулевых целых чисел есть положительный наибольший общий делитель и что (Ь) любое целое а > О представимо в виде произведения положительных простых чисел единственным образом (с точностью до порядка, в котором расположены эти множители) Разумеется, если само число а простое, то «произведение положительных простых чисел» в (Ь) нужно понимать как состоящее из одного простого числа.
Алгоритм деления. Для любых двух ненулевых целых a u Ъ существует единственная пара целых q и г, называемых соответственно частным и остатком^ таких, что
а = b q + г, 0 < г < \Ь\.
Отсюда следует, что "а и (а, й) — й тогда и только тогда, когда 丁 = 0. При г / 0 легко показать, что общий делитель чисел а и b также делит г, а общий делитель чисел й и г также делит а. Тогда (a,b)\(b^г) и ®т)|(а,Ь), так что (а, Ь) = (Ь, г). Далее, либо г\Ь^ либо т/ 瓦 В последнем случае с помощью алгоритма деления получаем
b = т * 红 +『1,0 < ri < г.
Опять же, либо п|г и (а,Ь) = и, либо с помощью алгоритма деления находим
丁 =『1 * 02 +『2, G < 丁2 V『1
и (а, й) = (Ьу г)=(『,『1) =(Г1,г2). Если предположить, что этот процесс будет продолжаться, то поскольку остатки 丁匕 丁2,•…образуют монотонно убывающую последовательность неотрицательных целых чисел, в конце концов одно из них должно обратиться в нуль. Предположим, что процесс оканчивается так;
(&)Т>_3 = Гк-2 * ^-1 + TI 0 < Гк-1 < Т>—2
(&+1) Т>—2 =也―1 *欤 +也0 <Vk < Vk^
(七 + 2) rk-r = П:* Qk+i + 0.
Тогда
(a,b) = (b,T)= (r,n)=…=(n-2,^fc-i) =(П-1,П)- rk. Поскольку т = а—b*q = a+ (—g) ^ b = mi ^ a + ny ^ b. находим
~ b — r qi
=ft — (mi * a + Th * b) * Qi
=—mi * Qi * a + (1 —冗]* qi) * b
=g * a + 冗2 * a
『2 =『_『]* Q2
=(mi ^ a + nx ^ b) ~ (加2 * a + 冗2 * b) * Q2
=(mi — Q2 * ni2)* a + (冗 1 — Q2 * 冗2) * b
=772.3 * a + 冗3 * b.
Продолжая, получаем в конце концов
夕化—m*比+1 * а +几加+i * b.
Таким образом, мы доказали следующее утверждение.
Теорема. Если d = (а, b), шо существуют такие m, n е Z, что d = (а, b)— m * а + п * й.
В представлении (а, Ь) = пг * а + 冗 * b целые тип определены неоднозначно; в самом деле, (a, b) = (m + k*b)^a-^(n —k^a)^b для любого А: / N. Важность этой теоремы вытекает из следующего. Если а|с, Ь\с и если (a, b) = d, то a^b\c^d. Поскольку а\с и Ь|с, то существуют такие целые s и ^, что c = a*s = b*t. По условию теоремы найдутся такие m,п е Z, что d = m*a + n*b. Тогда
c*d = c*m*a + c* 冗 *b = b* 力*т*а + а*£*тг*Ь = а*Ь* (力 *7п + в*тг) и а * “с * d.
Второе следствие алгоритма деления — это
Теорема. Любое непустое множество целых чисел К, замкнутое относительно бинарных операций сложения и вычитания, — это либо {0}, либо оно состоит из всех элементов, кратных его наименьшему положительному элементу.
Пусть для данных a,b е Z .существуют такие m,n € Z, что а^т + b^n = 1. Но любой общий множитель чисел а и & является множителем правой части, т. е. числа 1; следовательно, (a, b) = 1. Два целых числа а и Ь, для которых (а,匕)=1, называются взаимно простыми.
Теорема единственности разложения. Каждое целое а > 1 может быть единственным способом (с точностью до порядка сомножителейJ представлено в виде произведения по лож. итель ных простых чисел:
а = Pi *「2 * Рз * …* Рп・
Очевидно, что
-а — -(pi *「2 *Рз * •…*р«).
Кроме того, поскольку зтир необязательно все разные》мы можем записать а=р『*若2 ^ра3 疗,
где каждое ot,i>l и все pi, Р2 5 Рз, - • -)Ps различны.
Пусть m есть положительное целое число. Отношение сравнимости по модулю т,(三(modm)), определено для всех а, ft € Z следующим условием: а = Ь (modm) тогда и только тогда, когда ш|(а — ft). Альтернативное определение, насто более полезное, чем приведенное выше, таково: а = b (mod m) тогда и только тогда, когда числа о и й при делении на m дают один и тот же остаток. Из этих определений немедленно вытекают следующие результаты:
ииТеорема. Если а 三 b (modm), то для любого rzEZ, тп*тг + а 三 b (modm) обратно.
и
и
Теорема. Если а 三 b (modm), то для всех wWZ, а + 工 三 % + ж (mod m) u а*土三 b*z(modm)・
Теорема. Если а 三 fe(modm) u с = e(modm); mo а + c = b + e^modm), a~c = j: /mod m), a*c = b^ e(modm).
Теорема. Пусть (c,m) = d. Запишем m = mi ^ d. Если с * а 三 c * fe(modm); mOa^ b(modmi) u обратно.
Отношение = (mod m) на Z является отношением эквивалентности, разбивающим все целые числа на т классов эквивалентности [0], [1], [2],... ,[т-1], которые называются классами вычетов по модулю т, где
[г] := {а : а € Z, а = r(modm)}.
Обозначим через Z/(m) множество всех классов вычетов по модулю т. Два основных свойства классов вычетов по модулю m таковы: если а и b принадлежат к одному классу вычетов [s], то а = fe(modm); если [s] и 同 суть разные классы-вычетов, то а 壬 fe(modm).
Рассмотрим линейное сравнение по модулю а^х = b(mod m),
в котором a, A m суть фиксированные целые числа и m > 0. Под решением сравнения мы понимаем любое целое w = wi, для которого выполняется т|(а * £] — Ь). Но если £i есть решение, так что т\(а * £i — ft), то для любого k С Z выполняется 7nl(а * (ni + к * тп) — &), так что Hi + k * 7n является еще одним решением. Таким образом, если 工1 — это решение, то решением является и любой другой элемент класса вычетов [zj по модулю т. Если у линейного сравнения есть решения, то они состоят из всех элементов одного или более классов вычетов кольца Z /(т).
Пусть (а, б) = 1 = s^a + t^m. Тогда b = b^ s^a + b^t^m^n хг = Ь*в является решением. Теперь допустим, что 女 壬 xi(mod m) — это еще одно решение. Поскольку а^ xi = fe(modm) и а * 工2 三 &(mod m), из свойства транзитивности отношения = (modm) следует, что а*б 三 a^X2(modm). Тогда т\а^ ⑵一万) и 工1 三 Ж2 (modm), вопреки допущению. Поэтому имеется только одно решение, скажем 工匕 и класс вычетов [xi] е Z/(m), называемый также классом сравнений по модулю, включает все решения.
Теорема. Сравнение а*£ 三 fe(modm) имеет решение тогда и только тогда, 先°zda d = (a, m) является делителем числа Ь. Если d|b, это сравнение имеет 「°8外。d несовпадающих решений (d классов сравнений по модулю).
Число 827 016 можно записать в виде
827016 = 8 * 105 + 2 * 104 + 7 * 103 + 0 * 102 + 1 * 10 + 6.
В этом представлении используются свойства сравнения целых чисел по моду- Л1°* Так, пусть а — целое число. В силу алгоритма деления
Если до = о, мм ТеперьЕсли до = о, мм Теперьесли Q]если Q]а = 10 * Со +『о,0 < го < Ю.
Если до = о, мм Теперь
Если до = о, мм Теперь
если Q]
если Q]
записываем а =『о. Если q° > 0, то go = 10*Qi +『i, 0 W 丁1 < Ю- 二 0, то а = 10 *『1 +『2, и мы записываем а =『i『o; если qi > 0,
то Q1 = 10*02 +『2, о W『2 < 10- Снова, если q2 = 0, то а = 102 *г2 + Ю*п +г0, и мы записываем а = 丁2丁1丁0; если Q2 > 0, мы повторяем этот процесс. В конце концов он должен оборваться, и мы получим
а = 10$ * % + 10‘т * %—1 + .…+ 10 * 丁工 +『о = rsrs_i .. .Г1Г0.
Это следует из того факта, что qi образуют набор убывающих неотрицательных целых чисел. Заметим, что используемые в этом представлении символы % принадлежат множеству {0,1,2,3,... ,9} вычетов по модулю 10. Такое представление единственно. Процесс не зависит от выбранного основания, и в качестве него можно взять любое другое положительное целое. Так, если в качестве основания взять 4, то любое положительное целое будет представлено некоторой последовательностью символов 0, 1, 2, 3. Например, десятичное число 155 представимо в четверичной системе как
155 = 43*2 + 4?*1 + 4*2 + 3 = 2123 по основанию 4.
Далее мы опишем алгоритм, порождающий последовательность простых чисел и называемый «решето Эратосфенам. Он был разработан в третьем веке до нашей эры. Этот алгоритм находит все простые числа, меньшие заданного целого ^. Он работает, удаляя все составные числа, и то, что остаётся, является последовательностью простых чисел.
Рассмотрим следующую последовательность целых чисел > 1:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ...
Первое простое число — это 2, и все числа, кратные 2, составные. Поэтому мы вычеркиваем все чётные числа, большие 2:
2 I 3 4 5 6 7 8 9 40 11 45 13 44 15 4^ 17 物 19 00 21...
Следующее невычеркнутое число этой последовательности — число 3. Значит, это и есть следующее простое число. Аналогичным образом мы вычеркиваем все числа, кратные 3. Заметим, что мы пытаемся вычеркнуть числа 3x2 = 6, 3x4= 12,3x6= 18, которые уже были вычеркнуты ранее, так как они кратны 2. Действительно, мы могли бы уменьшить количество операций, вычеркивая только 3(3 + 0) = 9, 3(3 + 2) = 15, 3(3 + 4) = 21,…
2 3 I 5 7 9 11 13 4^ 17 19 沿 23 25 沿 29 31 芻 35 37 …
В общем случае, начиная с некого простого числа р, мы последовательно вычеркиваем числа, кратные р2, р(р + 2), р(р + 4)Мы начинаем процесс вычеркивания с р?, поскольку все кратные р числа, меньшие этого, уже должны были быть вычеркнуты на более ранних стадиях процесса. Например, начиная с простого числа 5, мы вычеркиваем 5(5+0) = 25,5(5+2) — 35, 5(5+4) = 45,…. Нам нет нужды вычеркивать 5x2 или 5x3, так они уже были удалены на стадии р = 2 и р = 3 соответственно.
Заметим, что в ходе этого процесса нам всё же придется иногда вычеркивать уже вычеркнутые ранее числа. Так, 5(5 + 4) = 45 уже было вычеркнуто как кратное 3. Последовательность для р = 5 выглядит так:
2 3 5 I 7 11 13 17 19 2329 3137 41 43 47 53…
Ми продолжаем зт°т процесс, пока не достигнем простого р, для которого 2〉М где N — наибольшее число, которое мы проверяем на простоту. На :той стадии все составные числа < N уже должны оказаться вычеркнутыми. То что у нас останется, — это последовательные простые числа < N. Ниже мы 口 иводим список чисел, меньших 100 и полученных с использованием описан-
нОГо выше алгоритма:
31 37 4189 9731 37 4189 972357111317192329
31 37 41
89 97
31 37 41
89 97
43475359616771737983
2・3・ Рациональные числа
Множество рациональных чисел Q можно построить аналогично построению множества Z следующим образом. Пусть Е есть отношение эквивалентности на множестве Z х (Z \ {0}), определенное выражением
(a, b)E(c, d) <=^> а * d — b с.
Определим Q как
Zx(Z\{0})/E.
Сложение и умножение определены на Q через каноническое отображение рЕ формулами
Ре@Ь) +ре(с,d) = Pe(q * d + b* с,b * d), Ре@Ь) x?e(c, d) = pE(a * c^b^d).
Можно показать, что так определенные операции превращают Q в поле и что существует естественное инъективное отображение Z в Q; сохраняющее операции умножения и сложения. Мы можем, следовательно, рассматривать Z как подмножество Q.
Каждой упорядоченной паре (а,Ь) из множества Z х (Z\{0}) соответствует дробь а/Ь с числителем а и ненулевым знаменателем Ь (из-за необходимости сделать Ь отличным от нуля в определении Q использовалось Z \ {0} вместо Z). Две дроби тогда эквивалентны, если соответствующие им упорядоченные паРЬ1 эквивалентны в определенном выше смысле и рациональное число есть класс эквивалентности дробей.
Теперь определим два особых рациональных числа:
нуль Т Ре(0, &), единица т р^(а,а), и °братные элементы:
(аддитивный):—рЕ(а,й) = рЕ(—а,Ь),
(мультипликативный):р^^^Ь)-1 —Ре[Ь^
В дальнейшем мы будем пользоваться обозначением а/b. Сложение и умножение подчиняются дистрибутивному и ассоциативному закону. Кроме того, для ^ждоро а/b (а, b # 0) существует обратный элемент относительно умножения 0/а, такой, что (a/b) * (b/а) = 1.
Вычитание и умножение определяются формулами
b^d ^ 0 иb^d ^ 0 иа * d b * са * d b * са с _ a * d — b с b d b^ d
b^d ^ 0 и
b^d ^ 0 и
а * d b * с
а * d b * с
На множестве рациональных чисел существует отношение порядка. Элемент a/Ь € Q называется положительным тогда и только тогда, когда а * b〉0. Аналогично, а/b называется отрицательным тогда и только тогда, когда а * й < 0. Поскольку в силу закона трихотомии либо а * b〉0, либо а * 6 > 0, либо а * b = 0, из этих определений следует, что каждый элемент Q либо положителен, либо отрицателен, либо равен нулю. Отношения порядка < и > на Q определяются следующим образом: для любых ду € Q
х < у тогда и только тогда, когда х — у < х > у тогда и только тогда, когда х ~ у > 0.
Эти отношения транзитивны, но не рефлексивны и не симметричны. Q также удовлетворяет закону трихотомии: если £, у € Q, то выполняется одно и только одно из следующих трех соотношений:
(а" = у, (b)kVy, (с) ж > у.
Рассмотрим произвольную дробь s/m € Q, где m 0 0. Пусть (положительный) наибольший общий делитель чисел sum есть d; запишем в = d * ^i и т = d^mi. Поскольку (龜ш)〜(si, mi), то s/m 二 Si/mi. Поэтому любое рациональное число 我 0 может быть (единственным способом) записано в виде а/Ь, где а и b взаимно простые. Всякий раз, когда s/m было заменено на а/b, мы будем говорить, что дробь s/m приведена к несократимому виду. Всякое вводимое впредь произвольное рациональное число будет считаться приведенным к несократимому виду.
Теорема. Если х и у суть положительные рациональные числа и х < у} то 1/w > 1/у.
Свойство плотности. Если х и у, где х < у, суть два рациональных числа, то существует рациональное число N, такое, что х < z < у.
Архимедово свойство. Если х иу суть положительные рациональные числа, то существует положительное целое число р, та 冗。е, что р^ х > у.
Рассмотрим положительное рациональное число а/b, где b > 1. Тогда
а ~ qo ^b + vq, 0 $ г1。V b и 10 * г0 = gi * ft + и, 0 < ri < ft.
Поскольку го < Ь и, следовательно, Qi * b +『1 = 10 * 丁。V 10 * Ь, то qi < 10. Если ri = 0, то
丁。=行*6, a = q0*b+ ~*b, - = q0 + —.
Запишем a/b = q。• Qi и назовем q° • q^ десятичным представлением дроби а/Ь. Если 丁工 7^ 0, то получим
10 *『1 = 02 * b +『2? 0 < Г2 < Ь,
е < 10. Если『2 = °,『о 修=器* b, так что 丁。=瑞*匕+器*Ам З^тндное представление дроби a/b есть q。• qiQ2・ Если r2 = 丁匕 то десятичное ^едставление дроби а/ft —это периодическая дробь
Qo ' 0102 02 02 • ■ • •
Если Г2 R 0,мы повторяем этот процесс. Заметим, что различные остатки 6 门 суть элементы конечного множества {0,1,2,3,... , b - 1} вычетов по модулю ft, так что в крайнем случае гь должно совпасть с одним из остатков 丁 丁]『2,….,丁匕一1,скажем с гс, и тогда десятичным представлением дроби а/Ь будет периодическая десятичная дробь
Qo • Q1Q2Q3 • • ■ Qb-lQc+1%+2 - • - g&—1Qc+1Qc+2 ■ • •-
Таким образом, каждое рациональное число может быть выражено либо обрывающейся, либо периодической десятичной дробью.
Пример. Для дроби 11/6 находим
11 =
=1*6 + 5;
Qo :
二 1 严0:
二 5,
10 * 5 =
二 8 * 6 十 2;
Qi 二
二8严1二
二 2,
10 * 2 二
二 3 * 6 + 2;
Q2 :
二 3 严2 二
二 2 = в
и 11/6 = 1.833333....
И наоборот, ясно, что каждая обрывающаяся десятичная дробь является рациональным числом. Например, 0.17 = 17/100, а 0.175 = 175/1000 二 7/40.
Теорема. Каждая периодическая десятичная дробь является рациональным числом.
В доказ атель ств е используются две вспомогательные теоремы:
Каждую периодическую десятичную дробь можно записать как сумму бесконечной геометрической прогрессии.
Сумма бесконечной геометрической прогрессии, знаменатель которой г удовлетворяет условию \r\ < 1, есть конечное число.
2・4・ Вещественные числа
Начав с N, мы можем построить Z и Q, рассматривая фактормножества подходящих прямых произведений. Однако подобным образом невозможно построить R, множество всех вещественных чисел, и здесь требуются иные методы.
Сначала заметим, что отношение порядка <, определенное на Q, обладает тем свойством, что для любых a, ft € Q, таких, что а < Ь, существует с С Q, для которого а < с п с < Ь. Пусть 0(Q) есть степень Q, т. е. 0(Q) обозначает множество, элементами которого служат подмножества Q. Теперь рассмотрим упорядоченные пары элементов P(Q), скажем (4, В), удовлетворяющие следу- ◎Цим условиям:
(0 &UB = Q, 4ПВ = 0, gi) А и В оба непусты, (iii) Если а е A n b Е В^ т а < Ь.
Такая пара множеств (4, В) известна под названием дедекиндова сечения. На множестве сечений можно определить соотношение эквивалентности R выражением
(厶,8)風0,0)
тогда и только тогда, когда существует хотя бы одно рациональное число, принадлежащее либо одновременно А и 0, либо одновременно В и С. Это гарантирует, что сечения ({工比 < q}, {х\х > q}) и ({工区 < q},{w|工 > q}) эквивалентны для всех g € Q. Каждый класс эквивалентности относительно этого отношения определяется как вещественное число. Множество всех вещественных чисел, обозначаемое символом R, тогда является множеством всех таких классов эквивалентности. Если данный класс содержит сечение (Л, В), такое, что Л содержит положительные рациональные числа, то этот класс является положительным вещественным числом, тогда как если В должно содержать отрицательные рациональные числа, то этот класс является отрицательным вещественным числом. Так, например, V2, содержащий сечение ({ж(ж2 < 2},{£(工2 > 2}), —это положительное вещественное число, поскольку 1 € 4 = {ж|ж2 < 2}. Чтобы определить сложение вещественных чисел, нужно рассмотреть сечения (4i,Bi) и (Л2,^2), представляющие вещественные числа ai и 02・ Мы определим а】+&2 как класс, содержащий сечение (4з, Вз), где 4з состоит из всех сумм а = а】 +&2, полученных выбором а^ из 41 и а? из А^. Для заданного вещественного числа а, представленного сечением (А,В) определим —а, т.е. отрицательное а, как класс, содержащий сечение (—В^—4i), определенное соотношениями а € Ai 4=^> —а € —Л1 и b W 5工 4=> —b € —В^ Как мы увидим, а+(—а) = 0, и вычитание теперь можно определить формулой а — 3 = а + (—3). Из двух ненулевых чисел а и —а одно всегда положительно. То, которое положительно, называется абсолютной величиной или модулем числа а и обозначается |а|. Поэтому |а| 二 а, если а положительно, и |а| 二一а, если а отрицательно. Определим |0| = 0. Если m и а2 суть два положительных вещественных числа, то произведение а】* &2 一 это класс, содержащий сечение (^4, В4), где 4 состоит из отрицательных рациональных чисел, нуля и всех произведений а — а±^ а2, полученных выбором положительного а】 из 41 и положительного &2 из 厶2. Это определение распространяется на отрицательные числа соглашением, что если 因 и g положительны, то
(―因)* &2 = &1 * ( — &2)= —«1 * &2, (—9)* (—&2)= &1 * &2・
Наконец, определим
0*а = а*0 = 0 для всех а.
При этих определениях можно показать, что вещественные числа R образуют упорядоченное поле.
Сопоставив элементу q W Q класс, содержащий сечение
({z|z < q}, {z|z〉а}),
можно определить мономорфизм (полей) Q т R. Мы можем, следовательно, рассматривать Q как подполе поля R (т. е. отождествить Q с неким подпо-
нем
r) Те элементы поля R, которые не принадлежат Q, называются jjOJ^-^ 丿
/?WW
опальными
Теперь
можно
числами.
установить следующее важное свойство поля R: для любо-
непустого подмножества V С R, для которого существует верхняя грань ^ : е・ такой элемент М € R, что i/ < М для всех v eV, существует точ- М‘ верХНЯЯ грань (sup) L, такая, что если М — произвольная верхняя грань рства V, то L < М. Подобным образом мы можем определить точную ^ижпюю грань (配力 для любого непустого подмножества V С R, имеющего
нижнюю грань.
Не всякое подмножество поля R имееет верхнюю (или нижнюю) грань в R; например, ее нет для N. Чтобы преодолеть некоторые последствия этого фак- ^ в анализе часто пользуются расширенной системой вещественных чисел, обозначаемой R и состоящей из R и двух символов -оо и +оо, обладающих следующими свойствами;
СложениеЗакон замкнутостиЗакон коммутативности Закон ассоциативности Закон сокращения Тождественный элементА& Обратные элементыУмножениеМ> Закон замкнутостиМ? • Закон коммутативностиМ3. Закон ассоциативности М《・ Закон сокращенияMs・ Тождественный элементMs* Обратные элементыСложениеЗакон замкнутостиЗакон коммутативности Закон ассоциативности Закон сокращения Тождественный элементА& Обратные элементыУмножениеМ> Закон замкнутостиМ? • Закон коммутативностиМ3. Закон ассоциативности М《・ Закон сокращенияMs・ Тождественный элементMs* Обратные элементы
Сложение
Закон замкнутости
Закон коммутативности Закон ассоциативности Закон сокращения Тождественный элемент
А& Обратные элементы
Умножение
М> Закон замкнутости
М? • Закон коммутативности
М3. Закон ассоциативности М《・ Закон сокращения
Ms・ Тождественный элемент
Ms* Обратные элементы
Сложение
Закон замкнутости
Закон коммутативности Закон ассоциативности Закон сокращения Тождественный элемент
А& Обратные элементы
Умножение
М> Закон замкнутости
М? • Закон коммутативности
М3. Закон ассоциативности М《・ Закон сокращения
Ms・ Тождественный элемент
Ms* Обратные элементы
(а)Если в С R, то -оо < х < +оо и 工 + (+оо) = +оо, ж + (-оо) = -оо,
, ,-= —U.
+8 —ОО
Если £〉О, то £ * (+8)= +00,① * (-00)= -оо-
Если £ V О, то ① * (+оо) = -оо, х * (-оо) — +оо.
Заметим, что R не обладает всеми алгебраическими свойствами R. Далее будут перечислены основные свойства R — системы всех вещественных чисел.
т + в W R для всех г, 5 € R.
『 + £ = £ +『 для всех т, в е R.
т + (s + 力)=(т + в) + 力 для всех т, % 力 W R. Если 丁 + 力=s + i, то г = s для всех % s,力 W R. Существует единственный аддитивный тождественный элемент О € R, такой, что т + 0 = О + т для любого г € R.
Для любого г € R имеется единственный обратный относительно сложения элемент —т € R, такой, что 丁 + (—т) = (—г) + т = 0.
т * в G R для всех r,s€R-
т * s = s * 丁 для всех г, s € R.
т * (s * 方)=(т * б) * 力 для всех г, 5, i € R.
Если т*р = п*р, тт = ппр^0Е^ для всех m, n € R.
Существует единственный мультипликативный тождественный элемент 1 е R, такой, что т*1 = 1*丁 = T для любого г € R.
Для любого г 7^ 0 € R существует единственный обратный относительно умножения элемент 丁一1 е R, такой, что г *г-1 — r"1 *r = 1-
Распределительные законы
Dl
d2.
Свойство плотности
Для любых rf Sj t е R г * (s + i) =t*s + t* 力 (s-i-t)*r = s*r + t*r
Для любых т, в € R, где г < s, существует 力 € R, такое, что г < t < s.
Архимедово свойство
Для любых т, $ С R+, где г < а, существует n € N, такое, что п^т > s.
Свойство полноты
Для любого непустого подмножества R, имеющего нижнюю грань (верхнюю грань), существует наибольшая нижняя грань (наименьшая верхняя грань).
2・5・ Комплексные числа
Поле комплексных чисел^ обозначаемое С, можно определить несколькими способами. Рассмотрим R х R и возьмем в качестве элементов С упорядоченные пары (ж, у) е R х R. Операции сложения и умножения элементов С определяются формулами
(£1,力)+ (£2,銓):=(6 +£2,?1 +?2),
(£1,力)* (£2,?2):=(5£2 一u1?2/1?2 +z2?1),
Теперь мы покажем, что С является полем. Мы можем определить мономорфизм (полей)
г : R С формулой『(£)=(£, 0).
Это позволит нам рассматривать R как подполе поля С. Кроме того, можно проверить, что
(©у) = (н,0) + (0,1) * ®,о).
Поэтому, воспользовавшись определенным выше мономорфизмом, можно записать
(х,у) = x + i*y,
где х, у € R и г = (0,1). Видим, что
г2=(0,1)*(0,1) = (-1,0) = -1.
Для заданного комплексного числа
z = х + i у, где ж, у € R,
назовем х вещественной частью,冗(z), а у — мнимой частью^,(z), числа z. Число
называется комплексно сопряженным числа z и обозначается z (или z*). Очевидное геометрическое представление комплексных чисел — точки декартовой плоскости R х R. Это представление известно как диаграмма Арганда. Такая
строится с помощью двух перпендикулярных координатных осей quarp ти Нислу z = х + i * у сопоставляется точка с координатами(£,у). на м представлении сложение комплексных чисел интерпретируется как сло- 号謚:вектор。》на плоскости. Длина г отрезка
* (0,0) -(£,д)
цазыв аетсяцазыв ается例м самым имеем例м самым имеем\z\ = г =,①2 + ,2 =,^.\z\ = г =,①2 + ,2 =,^.абсолютной величиной или модулем числа z и обозначается \z\.
цазыв ается
цазыв ается
例м самым имеем
例м самым имеем
\z\ = г =,①2 + ,2 =,^.
\z\ = г =,①2 + ,2 =,^.
угоЛ который отрезок Oz составляет с осью Ох, называется аргументом (амплитудой или углом) числа z и обозначается argz. Итак, мы получаем
tg(argz) = tg® = -,
a arg% определяется как вещественное число по модулю 2тг (если z * °, поскольку arg 0 не определен). Некоторые авторы берут 0 < argz < 2тг, тогда как другие предпочитают —тг < argz < тг. Это ограниченное значение называется главным значением аргумента.
Координаты г и ^ называются полярными координатами. Соотношения между полярными координатами точки и комплексным числом z = х + i *у^ которое эта точка представляет, суть
х ~ т cos ^, y = TSin 仇 г = |z| = уж2 4- у2, 8 = argz, z = /(cosJ + sing).
Из геометрических свойств треугольника вытекает неравенство Mi + 3 < |^1| + |^|,
известное как неравенство треугольника. С помощью двух формул
E/| =也||力1 и
arg(ziZ2)三 aFg(zi) + arg(z2)(mod2тг)
можно дать геометрическую интерпретацию умножения комплексных чисел. Другие конструкции поля С таковы.
Рассмотрим множество М^ состоящее из всех матриц M2(R) вида
С операциями сложения и умножения матриц множество М образует поле, в котором нулем служит。2, а единицей — единичная матрица 4 • Получаем
(а —Ь、( с 一d \ _ ( ас — bd — (ad + 6с) \卜 b а у у d с J ~ у ad-ibc ас — bd у
Кроме того, отображение р : R т М. определенное формулой
р© = ("),
является изомофизмом между полем R и неким подполем поля М. Отображая матрицу
на а + г * Ь, получим изоморфизм между 7И и С.
Определим С как факторкольцо поля R[i] посредством главного идеала (i2 + 1), т.е.
C-R[i]/(i2 + l).
Если обозначить образ полинома i е R[i] при каноническом отображении через i, то каждый элемент С можно единственным образом записать в виде х + £у, где ①,у С R. Операции сложения и умножения в С суть естественные операции факторалгебры.
Для облегчения изучения некоторых кривых (например, логарифмической спирали) часто снимаются ограничения г>0и0<^<2тгна полярные координаты. Тогда получается расширенная система полярных координат, в которой г и ^ могут принимать любые вещественные значения. В этой расширенной системе любая пара (р,з) определяет единственную точку плоскости, но у каждой точки плоскости имеется бесконечное число полярных координат, а именно
(р,以 + 2тг),(一亿 а + (2т? + 1)7Г)
для всех п € Z. В этой расширенной системе логарифмическая спираль описывается одним уравнением г = е°°, тогда как в ограниченной системе для ее определения потребовался бы целый набор уравнений.
2.6. Векторы и матрицы
Пусть F есть поле. Векторное пространство (называемое также линейным пространством) V над полем F определяется как аддитивная абелева группа V вместе с функцией F xV —> V, (Л, v) т Л * v, удовлетворяющая условиям
Л * (а + Ь) = Л*а+Л*Ь,(Л + /^)*а = Л*а+^*а,(А * jli) * а = Л * (jLi * а),1 * а = а
для всех 入小 С F n a, b w V. F называется основным полем векторного пространства, его элементы называются скалярами^ а элементы V ——векторами. Буквы, обозначающие векторы, печатаются жирным шрифтом. Векторное подпространство векторного пространства V определяется как любое подмножество V1 множества 匕 для которого
V1 является подгруппой аддитивной группы V,
для любого а е V и любого Хе F справедливо Л * а е V7.
冰 ество всех кортежей (①小①九),где xi С R, i = 1, • • ■ ? ^, образует М"° пстранство, которое мы обозначим Rn, над основным полем R, векторное пр у
если определить
(£1,①九)+ (?/15 - - - 7 Уп) —(£】+ Ух,. -. , жп + Уп)1
Д *(£】,.*• ,£«,) = (入*,入* 土 п), 「де А С R*
Векторное пространство V над полем F, удовлетворяющее аксиомам коль- таким способом, что сложение в кольце является сложением в векторном пространстве и такое, что
Л *(V1 * V2)=(入* V1) * V2 = V1 * (Л * V2)
всех vi,V2 € V и Л € F, называется алгеброй над F. Если, кроме того, :::образует коммутативное кольцо, мы будем называть его коммутативной алгеброй. Подмножество алгебры V называется подалгеброй, если оно одновременно является векторным подпространством и подкольцом алгебры V.
Для двух данных векторов х =(£],£2,£з), У = (?Л,У2,Уз) С R^ определим их векторное произведение, обозначаемое х х у, как вектор
х х у :=(£2 *Уз - £з * У2,£з * У1 - 3 * Уз,£] * 伙 一 £2 * У])・Векторное произведение некоммутативно. Это пример алгебры Ли. Получаем
хXXXхXXXXXXXXXXX(у + Z)= X X у + X X Z, у = -у X Z, (入* у) = X*(X X у),
х
X
X
X
х
X
X
X
X
X
X
X
X
X
X
X
(у X Z)+ Z X (х х у) + у X(Z х х) = 0.
Последняя формула называется тождеством Якоби.
Для двух векторных пространств U и V над полем F гомоморфизмом U на V называется отображение t: U ^ V, удовлетворяющее соотношениям
i(a + b) = i(a) + i(b),力(入* a)=入* (a)
Дм всех a, b е t7 и Л е F. Гомоморфизмы векторных пространств поэтому сохраняют линейные комбинации вида
Ai * ai -Ь Аг * а.2 + ... + Лп * ап,
Р“е Ai,... , Лп е F, ai,... , an G U. По этой причине гомоморфизмы векторных пространств называются линейными преобразованиями или линейными ото- ?仅邮£州^儿口・ Множество всех линейных комбинаций векторов ai,... , an С образует векторное подпространство пространства U, называемое подпространством, порожденным ai,... , ап (или натянутым на ai,... , ап). Вектор- "°е пространство V называется конечнопорожденным, если существует конечное множество элементов а】,а^, порождающее V.
Векторы ai,... , ап называются линейно независимыми, если единственный Wop Л1,…,удовлетворяющий соотношению
入 1 * а^ +入2 * а? + .…+ 入?! * &n — 0,0 € V,
2 - 154R
есть
Xi =入2 =…=Хп = О, О G F.
Векторы ai,... , ап являются линейно зависимыми тогда и только тогда, когда существуют Л1,... , Лп, не все равные нулю, для которых
入I * а】+ 入2 * а? + .…+ Ап * а?! = 0.
Если ai,... , ап порождают некое векторное пространство V и являются линейно независимыми, то мы будем говорить, что аг,... , ап образуют базис пространства V. Если а】,…卧 образуют базис пространства V и b есть произвольный вектор пространства V, то существует единственный кортеж (Л1,... , Лп), такой, что
b =尢 * а^ + 1A 2 * а? + ….+ А л * а^.
Скаляры Лх,... , Лп называются тогда координатами или компонентами вектора b относительно базиса ах,... , ап. Можно показать, что каждое конечнопо- рожденное векторное пространство V имеет базис и что, в частности, любые два базиса пространства V содержат одно и то же число элементов, скажем, п. Число п называется размерностью векторного пространства V над полем 歹,и мы будем писать dim V = п. Векторное пространство V тогда изоморфно Fn, т.е. векторному пространству всех кортежей (①小...,£九),где ①名 € F, i = 1,... 7п. По определению dim{0} = 0.
Если U и V суть конечномерные векторные пространства над одним и тем же полем F и если «сложение» линейных преобразований и «умножение линейных преобразований на скаляр» определить формулами
(土1 +±2)(а)=九(а) +力2 (а),
(Л * 力)(а)二入1 * (力(а)) для всех а С U,
то легко показать, что множество линейных преобразований само образует конечномерное векторное пространство над F (имеющее размерность (dim U) х (dim V)). Это векторное пространство обозначается Hom(t7? V). Пусть i С Hom(U, V), и】,… ,un — базис пространства U, a vi,…,vm — базис пространства V. Тогда i полностью определяется формулами, показывающими, как компоненты (£:,….,①^) вектора 土(х) относительно базиса vi,... , vm пространства V можно выразить через (д,…,①九),т.е. через компоненты вектора ① относительно базиса их,... , un пространства U. Получим
£〔 = ацЖ1 + 6112^2 + •…+ aln^n5£$ = «12^1 + 倣2£2 + …+ d2n^n,
①;n = Sml^i + &Tn2£2 + •…+ amn^n, ^ij G f
гЬгЬиниенты aij определяют гомоморфизм t однозначно по отношению 「А* [бранным базисам. Прямоугольный массив коэффициентов
/ ЙЦ^12■ - •^1п、
@21^21■ - •а2п
\ ^ml^m2■ - -^mn/
называется матрицей А, имеющей т строк и п столбцов. Более абстрактно ^ожно представлять себе матрицу как функцию
А : {1, 2,... .т} х {1,2,..., п} т F.
Матрицу А часто сокращенно записывают в виде (仅叮),и чтобы показать, что она представляет линейное преобразование t, мы будем писать (i) = (а仃).Матрица, соответствующая нулевому отображению 力 х т 0 (для любого х G.U), называется нулевой матрицей и обозначается знаком 0.
Сумма двух (т х п)-матриц А —(值牙)и В =(仇力 определяется как (т х п)- матрица
В ~ (а订 + Aj).
Произведение (m х тг)-матрицы А =(值盯)на скаляр А определяется как (т х п)- матрица X * А ~ (Л * а^). С этими определениями множество всех (т х п)- матриц с коэффициентами из поля F становится векторным пространством с размерностью т * п, изоморфным Hom(E7, V). Мы определим произведение С = А* В двух матриц А (仅仃 является (т х тг)-матрицей) и 8 他讣 является (^ х р)-матрицей) как (т х р)-матрину (с^), где
片1
Такое определение мотивировано потребностью образовывать композицию ли- ^йных преобразований s。力 С Hom(E7, W), если даны преобразования 力 С 0m(U, V), соответствующее матрице В, и 5 G Hom(V, W), соответствующее матрице А. Матричное произведение А* В матриц Л. и В не определено, если только число столбцов матрицы А не равно числу строк матрицы В (равно- "У размерности пространства V). Существование произведения А* В поэтому Не означает существования произведения В * А. С так определенным умно- ^нием множество квадратных матриц порядка п, т. е. матриц с п строками ” столбцами, и с коэффициентами из кольца К, образует некоммутативное ^°^ьцо (если п > 1 п К ^ {0}), обозначаемое Мп(К). Матрица А С Мп{К) на- нто еТСЯ нее仪poo/cdtHHo夜 или обратимой^ если существует матрица В, такая,
А* В = В * А = 1п^
где 1П является тождественной матрицей (называемой также единичной матрицей) порядка п, коэффициенты которой равны 5打・(дельта-символу Кронекера). Последний определяется как
если i = j}
О в противном случае.
Матрица 8 тогда определяется единственным образом и называется матрицей, обратной к матрице А; она обозначается символом А-1. Если в М„(К) нет такой матрицы В, для которой А * В — В * А — 1п^ то матрица А называется вырожденной или необратимой в МП(К). Линейное отображение U т V будет являться изоморфизмом векторных пространств в том и только том случае, когда его можно представить некой обратимой (квадратной) матрицей. Матрица, транспонированная по отношению к (т х п)-матрице А — (а仃),определяется как (ri х т)-матрица (&订),полученная из А заменой ее строк ее столбцами. Матрица, транспонированная к матрице А, обозначается Ат. Если А 二 Ат3 то матрица А называется симметричной.
Для любого п > 0 множество невырожденных (п х п)-матриц над полем F образует мультипликативную группу, называемую общей линейной группой GL(n, F). Элементы группы GL(n, F), имеющие определитель 1, образуют подгруппу, обозначаемую SLg F) и называемую специальной линейной группой. Матрица размера mxl, у которой есть только один столбец, называется вектор- столбцом, а матрица размером 1 х п называется вектор-строкой. Уравнения, определяющие гомоморфизм i с матрицей А, можно поэтому записать в виде
X’ = А * X,
где х‘ — это вектор-столбец, т элементов которого суть компоненты отображения *х) относительно vi,... , vm, ах — вектор-столбец из п элементов, являющихся компонентами вектора х относительно Ui,... ,un.
В частности, (1 х п)-матрица описывает гомоморфизм из n-мерного векторного пространства U с базисом Ui,... , un в одномерное векторное пространство V с базисом Vi,Множество всех (1 х п)-матриц образует векторное пространство, изоморфное векторному пространству Hom(U)F) всех гомоморфизмов, отображающих U на его основное поле F, которое можно рассматривать как векторное пространство размерности 1 над самим собой. В общем случае, если V — произвольное векторное пространство над полем F, то векторное пространство Нот(匕 F) называется сопряженным к V пространством и обозначается V* или V. Элементы Нот(匕 F) называются линейными функционалами. Можно показать, что любое конечномерное векторное пространство изоморфно сопряженному к нему.
Пример. След квадратной матрицы — это сумма ее диагональных элементов. Поэтому след квадратной матрицы является линейным функционалом. Это означает; что
tr(A + В) = tr(A) + tr(B),tr(c * 4) = с * tr⑷.
ных подпространств S и Т векторного пространства V определим Для ДВУХ д、
厶S + Т := {х + у\х е S,y е Т}.
S 十 Т является подпространством пространства V. Мы будем называть Тогда ;о位 суммой S и Т (записывается S ㊉ Т) тогда и только тогда, когда у пр% + т 出 $ с т = {0}. S и Т называются тогда прямыми слагаемыми “ — анства V. Всякое подпространство S конечномерного векторного про- ПР° я у является прямым слагаемым для V. Кроме того, если V = S ®Т, странства у то
dimT = dimV — dimS.
Если S и Т — любые конечномерные подпространства векторного пространства
кTо
dimS + dimT = dim(S G Т) + dim(S + T).
2・7・ Кватернионы
Подходящим образом определив умножение на R х R,можно построить поле С, являющееся расширением поля R. В самом деле, поскольку С — это векторное пространство размерности 2 над R, то С есть коммутативная алгебра с тождественным элементом над R. Естественно попытаться повторить этот процесс и попробовать погрузить С в некую алгебру, определенную над Rn (тг > 2). На самом деле невозможно найти такое расширение, которое удовлетворяло бы аксиомам поля, но, как показывает приведенное ниже построение, некоторого успеха достичь удается. Рассмотрим ассоциативную алгебру ранга 4 с базисными элементами
/, Л К, где 1 ——тождественный элемент, т. е.
1 * Z = Z, =1*К = к.
Попарные произведения этих элементов суть
/*/=J*J=K*K=—1, I*J = K, J*K = I,К * I = J, J *T = —К, K*J=-I, /*К = —J
°т° так называемая алгебра кватернионов. Умножение, определенное выше, ^невидным образом некоммутативно, поэтому полученная структура не может ть п°лем. Это кольцо с делением и есть алгебра кватернионов. Она ассоциативна.
Любой кватернион q можно представить в виде
Q а^ * 1 а/ * I aj * J + а^ * X, где 团,仅八 aj, &к С F.
Сумма, разность, произведение и частное двух кватернионов
q := &1 * 1 + а『* / + 力 * J +* К,
р by * 1 + bi * I + bj * J + Ьк * К
определяются следующими формулами:
q + р := («1 + 61) * 1 + (а/ + 6/) * / + (aj + bj) * J + (&к + 6к) * К,
q — р = (&1 —瓦)* 1 + (а? — Ь/) * I + (aj — bj) * J + (&宦一Ьк) * К, q*p :— (&i *l + a『*/ + aj*J + ак * К) * (瓦 * 1 + b『* / + bj * J + &k(£)
=(a】* bi — cli — aj * bj — ак * Ьк) * 1
+ (а】* b『+ а『* 瓦 + aj ** bj) * Z
+ (&i * bj + aj * b-y +* J
+ (di * Ьк +* bi a/ * bj — aj * b『)* K.
q/p := q *рТ, где「一】является элементом, обратным к р.
Элемент, обратный q относительно сложения,это
—q — —а】*1 — a/*I~aj*J — &к * К.
Элемент, сопряженный q, обозначаемый, скажем, q*, определяется так:
q* := a^ * 1 — aj * I — aj * J ~ &к * К.
Величина, обратная q, — это
Абсолютная величина q — это
|q|2 =&; + &; + &% + а%.
Нормированное значение q определяется как q/\q\>
Матричное представление кватернионов дается соответствиями
Q I =К T —2 ( 0 J = ”2,
где i :— v^T, a <7^,(7^, az суть спиновые матрицы Паули. Это также показывает, что алгебра кватернионов ассоциативна.
Алгебру кватернионов можно получить и другим способом — как подкольцо кольца M4(R), состоящее из матриц вида
2・8・ Многочлены
В этом раздое мы введем многочлены. Доказательства использованных здесь теорем можно найти в литературе [3], [21], [29]. Функции вида
1 + 2*£ + 3*£2,①+ /,—4**+ fio
52
многочленами от х. Коэффициенты в этих примерах — целые и называются числа. В обычном анализе область значений, принимаемых пе- рациона^ ①(область определения функции), есть R. В алгебре в качестве обла- ременно ^^^ многочленов берется С. Рассмотрим, к примеру, многочлен с1rli ?Р^^ 1 решением уравнения р(х) = 0 служит 士" Любой многочлен от ① Р^ — Читать отображением множества S (области определения) на множе- мс»к半(область значений) рассматриваемого многочлена. Возьмем, к примеру, ств° 1 + 6*£ —3**2・ Если S = Z, то Т С R, и то же самое справедливо, ""O’S 二 Q или S = R; если S = С, то T с С. Два многочлена от х равны, еслИ они совпадают по форме. Например, a^b*x = c + d*x тогда и только тХа, когда а = с и b = d.
Пусть R — кольцо, и пусть ①,называемый свободной переменной, является любым символом, не принадлежащим R. Многочленом от х над R мы будем называть произвольное выражение вида
a(①)=a0* ①° + a]* 储 + a2 * / + .…=〉:a比 * £比,a比 ё R^
к=0
где лишь конечное число а^ отлично от %, т. е. от нулевого элемента кольца R. два многочлена от х над R, а именно определенный выше а(х) и многочлен
03) = Ьо * £° + 瓦 * а? + 历 *+ …=£ 涼* 丿,6比 С R,
равны (записывается а(①)=0(化)))если ак = Ък для всех значений к.
В любом многочлене, вроде а(х), каждый из составляющих его компонентов ао*гг°, а】, &2 *£?, •… называется членом; в любом члене, вроде &公 *化\ а^ называется коэффициентом при данном члене. Члены многочленов а(х) и 0(化) были выписаны в заданном (но естественном) порядке. Индекс г, являющий
ся верхним индексом при х, — это просто указатель положения члена а^ * хг в данном многочлене. Подобным образом запись а^ перед ①‘ в члене аг*хг не сле-
дует понимать как указание на умножение, а знаки плюс между членами следует воспринимать просто как полезные связки, а не как операторы. Пусть % —— нулевой элемент кольца. Если в произвольном многочлене, например в а(£), коэффициент ап ^ г, тогда как коэффициенты при всех последующих членах
суть г, то мы будем говорить, что многочлен а(①)имеет степень п, и назы- ватв;^ его старшим коэффициентом. В частности, многочлен а。* £° + % * ①^十 “* 化 + ... имеет степень нуль и старший коэффициент а。, когда а0 ^ z, и он не имеет никакой степени (и никакого старшего коэффициента), когда а。二 %.
Обозначим символом R[x] множество всех многочленов от х над R и для произвольных а(гг), 0(①)е R[x] определим сложение (+) и умножение * на R\x] Формулами
Q(①)+ 0⑺:=(«о + Ьо) * £。+ (例 + 瓦)* X1 +(02 + ^2)* ж2 + ...
=3:(a森 + 6比)* y,
a(化)*0(h):=&0 * 历* £° + (а。* by а^ * 瓦)* а?
+ (a0* 62 + 用*瓦 + a2 *瓦)*化2 + e 6比*£比, k=0
где
k
2=0
Сумма и произведение элементов R[x] являются элементами 五同,и имеется лишь конечное число членов с ненулевыми коэффициентами из R. Сложение в 五[①]ассоциативно и коммутативно, и определенное выше умножение ассоциативно и дистрибутивно относительно сложения. Кроме того, нулевой многочлен
z*x0 + z*x1+z*a72 + ... = £ z * хк е R[①]к—0
является адаптивным тождественным элементом, или нулевым элементом множества R[f], тогда как многочлен
-а(①)=-а。* £° 十(—ai) * ж1 + (―仅2)* ж2 + ... = £(-&£)* 丿 G R[x]
为=0
является обратным элементом для а(①)относительно сложения. Итак, справедлива
Теорема. Множество всех многочленов R\x] от х над кольцом R является кольцом относительно определенных выше сложения и умножения.
Пусть а(£)и 0(z) имеют степени тип соответственно. Если т ^ п, то степень а(£)+0(①)равна большему из чисел т, п; если m = тг, то степень а(化)+ 0(①)не превосходит т. Степень а(①)* 0(n) не превосходит т -^ п, поскольку атЬп может оказаться равным z. Однако если в R нет делителей нуля, то степень произведения равна m + п.
Рассмотрим теперь подмножество S ;= {г * xQ : г е R} кольца R[x]^ состоящее из нулевого многочлена и всех многочленов степени нуль. Отображение
R-^S:r—^r*x°
является изоморфизмом. Поэтому мы можем в дальнейшем писать а。вместо а。* ①° в любом многочлене q (①)С R岡.
Пусть R — кольцо с единицей и. Тогда u = и * £° является единицей кольца Е罔,поскольку г/£° * а(①)= а(①)для любого а(①)€ R[x]. Кроме того, записав х 二 我*£工—z*xQ-\-u*x1 ^ имеем х С R[x]. Далее, а儿(х-х-х- до к сомножителей)二 а儿* М С 网①}, так что в выражении а(①)=а。+ а】* ① + 敗 * ①? + … мы можем рассматривать верхний индекс 5 в ①? как подлинный показатель степени, знак * в любом члене а{*хг как символ умножения в кольце многочленов, а связку + как символ сложения в кольце многочленов. Любой многочлен а(①)степени т над R со старшим коэффициентом и, т. е. с единицей кольца R, будем называть нормированным.
Теорема. Пусть R — кольцо с единицей w? и пусть а(①)二 a()+ai*£+. 一+am* хт G R[①]является либо нулевым многочленом, либо многочленом степени т,仅 0(①)=Ьо + bi *ж+.. . + bn*xn С R[x\ — это нормированный многочлен степени 72. Тогда существует единственная четверка многочленов ^(ж); гп(х),
(①)e R[x], где tr(*), ^l^) суть либо нулевые многочлены, либо име- 吧嘘ей <电т(т «т。
“т(i) a(z) = qr(n) */3(б)+tr(h),
a(化)=夕(化)*ql(z)+tl(h)・
广、 оремь1 мы утверждаем, что q(n) был поделен справа на /3(以 в резуль- В П ^его получились правое частное。五(‘)и правый остаток ‘&(*). Аналогич- 侬:п (丑)мы утверждаем, что °(“)был поделен слева на "(‘),и получились н°' частное Ql(n) и левый остаток 丁厶(*)・ Если tr(*) = г (соответственно ":::二 z), мы называем /З(с) правым (соответственно левым) делителем мно- гочлвнэ, а (а).
Рассмотрим теперь коммутативные кольца многочленов с единицей. Пусть д коммутативное кольцо с единицей. Тогда R[x] — коммутативное кольцо с единицей, и эту теорему можно переформулировать, не различая левые и правые частные (заменив Qr(n) = Ql ⑺ на q(z)), левые и правые остатки (заменив 『L(①)=tr(h) на т(%)) и левые и правые делители. Тогда пп. (i) и (ii) теоремы можно заменить на
a(z) — q(n) * 伙力)+ t(z)
и, в частности, мы получаем следущее утверждение.
Теорема. В коммутативном кольце многочленов с единицей многочлен a (①) степени m имеет х — b в качестве делигпеля тогда и только тогда, когда остаток
т = a。+ g * b +。2 * 俨 + …+ ат * хт — 2.
Если г — z^ то b называется нулем (корнем) многочлена a(z).
Если в R нет делителей нуля, их нет и в В\х\. В самом деле, допустим, что а(а) и 0(ж) суть элементы кольца R[x] степеней тип соответственно и что
a(①)* 0(z) = a。* б。+ (ao * 儿 + з * bo)z + 一 . + a^ * bnxm^n = z.
Тоцца каждый коэффициент этого произведения, и в частности ат * 6П, равен z・ Но R не содержит делителей нуля; поэтому ambn = z тогда и только тогда, ког^а &т = 2 или 6n = z. Поскольку это противоречит допущению, что степени многочленов а(/) и 夕(化)равны тип соответственно, то R[x] — это кольцо без делителей нуля.
Теорема. Кольцо многочленов R[x] является областью целостности тогда н Только тогда, когда кольцо коэффициентов R является областью целост-
Рассмотрение остатка
т = а° + 出 * b + 3 * 俨 + …+ am * 严указывает, что этот результат можно получить автоматически, повсюду в a(z)По Нив / на b и, разумеется, интерпретируя знак * как умножение в R. Так,
определению считая /(b) выражением, полученным подстановкой Ь вместо
立 повсюду в /(с), мы можем вместо т взять а(6). Это обычный процесс подста^ новки в элементарной алгебре, где ж рассматривается как аргумент, а не как свободная переменная. Для данного b W 冗 отображение
/(ж) —> /(&) для всех /(ж) W R[x]
является гомоморфизмом R[z] на R.
Самые важные алгебры многочленов — те, в которых роль кольца коэффи^ циентов играет некое поле F. Всякий ненулевой элемент поля F —это единица поля F. Для области целостности F[x] важнейшие результаты таковы:
Алгоритм деления. Если а(а), 0(a) W 尸財,где 0(化)片 г, то существуют и единственным образом определены многочлены q(化} г(ж), где г (ж) является либо нулевым многочленом, либо имеет меньшую степень, чем 0(a), такие, что а(а) = q(a) * 0(化)+ т(£).
Если г(ж) — нулевой многочлен, то 0(a) называется делителем многочлена а⑺,и мы пишем 0(z)|a(z).
Теорема об остатке. Если а(ж), а — b W F[H,mo остаток от деления а(а) на х — Ъ равен а(6).
Теорема о множителе. Если а(а) W F[x] u b W F, то х — Ъ является множителем многочлена а(а) тогда и только тогда, когда a(b) = 2; другими словами, х ~ b является множителем многочлена а(а) тогда и только тог- da, когда Ъ является нулем многочлена а(/). Это приводит к следующей теореме.
Теорема. Пусть многочлен а(а) W F[x] имеет степень m > 0 w старший коэффициент а. Если различные элементы 匕匕庆…一 ,bm поля F являются нулями многочлена а⑺,то
а(£)= а * (н —仇)* (/ —匕2)* …— bm)・
Теорема. Всякий многочлен a(z) W F[x\ степени m > 0 имеет 6 поле F самое большее m различных корней.
Теорема. Пусть a(z), 0(c) W F\x\ таковы} что a(s) — 0(б) для всех s ^ F. Тогда если число элементов в F превышает степени обоих многочленов а(а) и 0(立), то а(а) = 0(a).
В алгебре многочленов F岡 единственными единицами служат ненулевые элементы (т. е. единицы) кольца коэффициентов F. Поэтому единственными ассоциированными элементами многочлена a(z) w F[x] служат элементы вида V * а(с) области целостности F[a], где и — произвольная единица кольца F. Поскольку для любого и * z W F и любого многочлена а(а) W F[a]
a(z) =* а⑺ * %
и всякий раз, когда а(а) = q{x) */?(化),
а(а) = (и'1 * q(#)) * (и * 0(#)),
^едует, что (а) каждая единица кольца F и каждый ассоцииро- 『о отсюда нт многочлена а (а) является делителем этого многочлена и что занныи )|а(а), то любой ассоциированный элемент многочлена 0(a) так@) если "К делителем а(/)・ Единицы кольца F и ассоциированные элементы Же явЛ ются тривиальными делителями а(а). Все прочие делители а(ж), 殳(с) н^овые существуют, называются нетривиальными делителями. Много- е"且 / )W F[n] степени m > 1 называется простым (неприводимым) многочлен м〔ад F, если все его делители тривиальны.
"”;еперь рассмотрим область целостности многочленов С [ж]. Рассмотрим произвольный многочлен
0(化)=b0 + 瓦 * 力 + ь2 * 川+…+ 6m * ^m w C[a]
тепени тп〉1・ Приведем несколько элементарных теорем, связанных с нулями таких многочленов и? в частности, с подмножеством всех многочленов из С [ж], коэффициенты которых являются рациональными числами. Пусть т £ С есть нуль многочлена 0(a), т. е. 0(т) = 0, и поскольку 6"1 W С, то верно также 6—1 *g(r) = 0. Поэтому нули многочлена 队х) в точности совпадают с нулями ассоциированного с ним нормированного многочлена
а(а) = Ъ^ * 0(a) = а0 + ai * ж + а2 * ж2 + ... + 工 * е^т + жт.
При т = 1 многочлен
а(ж) = а。+ /
имеет нулем —а。,а при m = 2 многочлен
а(а) — ао + 0工化 + ж2
имеет два нуля:
У каждого многочлена хп — a W С[х] имеется п нулей над С. Существуют формулы, дающие нули всех многочленов степеней 3 и 4. Известно также, “то невозможно предложить формулу для нулей произвольных многочленов степеней m > 5.
Любой многочлен а (ж) степени m > 1 может иметь не больше т различных нулей. Многочлен а(а) — ag + ^i * ж + ж2 будет иметь два разных нуля тогда и ТОлько тогда, когда дискриминант а; — 4 * 的次 0. Тогда каждый из них н^нвае’с' 11Poeтым нулем многочлена а(а). Однако если а^ — 4 * а0 = 0, то приведенные выше формулы дадут в качестве нуля —| * а】.Тогда мы назовем — нулем кратности два многочлена а(ж) и выпишем эти нули в виде 一工* 1 ~
^сновная теорема алгебры. Всякий многочлен q(n) Е С[х] степени m > 1 ет в Q по крайней мере один нуль.
HojPeMa* Вс^ки 嚇 многочлен а(а) € С [ж] степени m > 1 имеет в С в точ- НуЛе ~ М ^улей, если каждый нуль кратности п считать за п из этих m
Теорема. Всякий многочлен а(а) W С [ж] степени т >1 либо имеет степе^ I, либо может быть записан как произведение многочленов W С [ж], каснсдъ^ из которых имеет степень 1.
Теперь мы изучим некоторые подмножества С [ж], ограничив кольцо коэф фициентов. Во-первых, предположим, что у многочлена
а(а) = ао + 团 * £ +。2 * $2 + • — + а„г * $т W 冗罔
степени m > 1 есть нуль вида т = а + В*Е,т.е. что
а(『)—а{)+ а^ * г + а2 * г2 + ... + ат * rm = s + t * i = 0.
Тогда получим
а ⑺=aQ + ai * г + а2 * г'2 + ...+ат *rm = s + t i = Q,
откуда вытекает следующая
Теорема. Если т W С является нулем произвольного многочлена а (а) с вещественными коэффициентами^ то г также является нулем а(ж).
Пусть г = а + b * 3 где 6/0, является нулем многочлена а(а). Тогда г — а — b i также является нулем, и мы можем записать
a(z) = [z — (а + 6 * £)] [z — (а — b * С)] * &工(化)=[ж2 — 2 * а * ж 4- а2 + 62] * «1 (ж),
где Qi (ж) — многочлен с вещественными коэффициентами, степень которого меньше на два, чем степень многочлена а(ж). Поскольку квадратичный многочлен с вещественными коэффициентами имеет мнимые нули тогда и только тогда, когда его дискриминант отрицателен, то справедливы следующие утверждения.
Теорема. Многочлены первой степени и квадратичные многочлены с отри- цательнъьм дискриминантом суть единственные многочлены из И[ж]? неприводимые над R.
Теорема, Многочлен нечетной степени из R[z] обязательно имеет вещественный нуль.
Предположим, что
月(£)= bo + Ьг * х + Ь2 * х2 +... + Ьт ^ хт W Q[z]・
Пусть с — наибольший общий делитель числителей коэффициентов 仇,a d — наименьшее общее кратное их знаменателей; тогда многочлен
a(z) = ^ * 0(a) = а()+ а± * х + а2 * х2 + ... + ат * хт е Q岡
имеет целые коэффициенты, единственными общими делителями которых служат ±1, единицы кольца Z. Кроме того, нули многочленов a(z) и 月(£)в точности совпадают. Если т W Q является нулем многочлена а(а), т. е. если
а(『)=а° + 肉 * у +。2 * 户 + …+ ат * 严=0,
то получим:
门伪 丁 e z, TO "ao,
*严一2 +…О,*严一2 +…О,(а) еСЯ 代 т e простой несократимой дроби, то (bj если т 二 s/,-
*严一2 +…
О,
*严一2 +…
О,
产% * Q(5/力)=а° * tm +。1 * 6 * tm 1 + Й2 * S2
5。s|so и t\am. Мы доказали следующее.
а Пусть а(а)二 а° + 小 * 化 + &2 * № + •…+ &т * 立‘"есть многочлен Теорема^〉】,целыми коэффициентами. Если s/t € Q, где ⑶力)=1, ^вллетпся нулем многочлена а (ж), mo б|ао и t\am.
Пусть а(а) и 0(a) — ненулевые многочлены из F[x]. Многочлен "(*) € F[x], обладавший следующими свойствами:
d(a) нормирован;
ф)|аИ и d^x^x),
для любого с(①)W F[a], такого, что с(①)|а(*) и с(*)|/3(с), выполняется ф)|ф)?
называется наибольшим общим делителем многочленов а(а) и /3(х).
Наибольший общий делитель двух многочленов из F\x\ можно найти тем же способом, что и наибольший общий делитель двух целых чисел.
Теорема. Пусть ненулевые многочлены а(ж) и/3(х) принадлежат F[x]. Нормированный многочлен
d(x) = 5(ж)* а(土)+ 右(2)* 夕(化),s(a)K(a) W F[矶 наименьшей степени является наибольшим общим делителем многочленов а(с) и 6(a).
Теорема. Пусть а(а) степени т > 2 и 0(a) степени п > 2 — многочлены из F[矶 Тогда ненулевые многочлены д(ж) степени не больше п — 1 и v(х) степени не больше т ~ 1, такие, что
“(с) * а(ж) + V(х) * /3(/) = г, где г — нулевой многочлен, существуют в F[x\ тогда и только тогда, когда а(а) и /3(а) не являются взаимно простыми.
Горема. Если。(化),0(化),р(±) € 歹[叫 ade а(ж) и р(а) взаимно просты, то из р(初&⑺* 夕⑺ следует, что р(х)\р(х).
Теорема о единственности разложения. Любой многочлен а(а) степени m 1 с° старшим коэффициентом а в F\x\ может быть записан в виде
а⑺=a*\pi(x)]ml * 出2(嘲俏2 * \pj(x)]mj,
Pi(x) — нормированные простые многочлены над F, a mi — положитель- Целые числа. Кроме того, с точностью до перестановки сомножителей ‘т° Р^злоакение единственно.
Дифференцирование
Пусть / : / т R есть некая функция, причем I — открытый интервал. Буде^ говорить, что / дифференцируема в точке a е I, если существует линейное отображение £ : R т R〉такое, что
lim上(堂負二/(④二妲=0.
Это линейное отображение, которое, если оно существует, является единственным, называется тогда дифференциалом от / (или производной от / в точке а) и обозначается daf. В обычных учебниках принято вводить дифференциалы df и dx и получать соотношения вроде
df,
ах
В современных обозначениях это соотношение имело бы вид
daf - f^dzT.
где Z(= id) обозначает тождественное отображение а т z. Если / и р дифференцируемы, из определения производной находим следующие правила диф- ференцирования суммы, разности, произведения и отношения функций:
也血+厶 -也血+厶 -производная суммы,производная суммы,也阪一壶 ^ d五也阪一壶 ^ d五разности,разности,-厶-厶/- g/- g出石旳豆* *+ -存一如必一血*g出石旳豆* *+ -存一如必一血*g—производная произведения,—производная произведения,gg
也血+
厶 -
也血+
厶 -
производная суммы,
производная суммы,
也阪
一
壶 ^ d五
也阪
一
壶 ^ d五
разности,
разности,
-厶
-厶
/- g
/- g
出石旳豆* *+ -存一如必一血
*
g
出石旳豆* *+ -存一如必一血
*
g
—производная произведения,
—производная произведения,
g
g
g 声 0 для х е I— производная отношения
d с
и соотношение —с 二 0, где с — постоянная.
Интегрирование
Система компьютерной алгебры должна быть способна осуществлять формальное интегрирование элементарных функций, например
/ dx 1 . /1 + ж\
Как правило, предполагается, что основное поле, в котором производятся вычисления, —это R. Символьное дифференцирование было реализовано на весьма ранних этапах развития систем компьютерной алгебры, тогда как символьное интегрирование (называемое также формальным интегрированием) было
踞踞mix m只,mix m只,дни системы значительно позже. Причина состоит в серьезном разли- 田eRCH° в формальным интегрированием и формальным дифференцировани- 1" ме^^ имрование — это алгоритмическая процедура, и знание производят- 〃м①?нтарнь1Х функций и правил дифференцирования суммы, произведе- ^ношения и сложной функции позволяет дифференцировать любую дан- М йЮ Единственная реальная трудность дифференцирования — это iyio ФУ полученного результата. Интегрирование, напротив, представляет- <ПРОПизвольным набором специальных приемов и частных случаев. Здесь есть '"'° лва общих правила, а именно правило интегрирования суммы и прави- ^^^рирования по частям. Если мы интегрируем сумму двух функций, то 1" 6щем случае мы интегрируем каждое слагаемое по отдельности, т. е.
踞
踞
mix m只,
mix m只,
(Л(ж) + /2(ж)) dx = 力(N)d》+/2(a)dc
Jto так называемое правило интегрирования суммы. Может случится так, что для суммы /1 + /2 имеется явная форма интеграла, но слагаемые /1 и Д реинтегрируемы в явном виде. Например,
+ (\nx)xx)dx =化。+ (\nx)xx)dx =化。
+ (\nx)xx)dx =化。
+ (\nx)xx)dx =化。
Однако интегралы
Xх dx^ (In x)xxdx
не могут быть записаны в замкнутой форме. Правило интегрирования суммы
можно применять лишь тогда, когда известно, что два из этих трех интегралов существуют. Для прочих комбинаций, кроме сложения (и вычитания), общих правил не существует. Например, хотя мы знаем, как интегрировать ехр ж и ж2, отсюда не следует, что мы можем проинтегрировать ехр(ж2). Для этой функ- Нии не существует более простого выражения, чем / exp x^dx. Поэтому мы заучиваем несколько «методов», скажем интегрирование по частям, интегрирование подстановкой, интегрирование просмотром таблиц интегралов и так “алее. Кроме того, мы не знаем, какой метод или какая комбинация методов сработает для данного интеграла. Далее мы будем излагать материал, тесно ведуя работам Дэвенпорта и др. [11], Маккалума и Райта [33] и Риша [42].
Поскольку дифференцирование явно проще интегрирования, уместно переФормуЛИрОВать 3aд^у интегрирования как «обратную задачу» дифференци- Рования,т. е. если дана функция /, то вместо того, чтобы искать ее интеграл 9’ мь工 будем искать такую функцию д, для которой dg/dx — /.
△ибо△ибо^^е^екение. Если даны два класса функций А и В, то задача интегрирова- ^1 °几兄 А и В состоит в том, чтобы найти алгоритм, который для любого ента / класса А либо дает элемент g класса В, такой, что / = dg/dx, доказывает, что в В нет такого элемента д, что / = dg/dx.
△ибо
△ибо
Hajif911PHMеР'“ ~ Q(a)仄石=Q(n), где Q(①)означает класс рацио-
н*х функций, то ответом для 1/х2 должно быть —1//, тогда как для 1/х
в этом множестве функций решения не существует. С другой стороны, е^ 3 = Q(a」ria), то ответом для 1/ж должно быть 1пж.
Рассмотрим теперь интегрирование рациональных функций. Будем pa^Q тать со случаем А = С(а), где С —это поле констант. Любая рациональна; функция / может быть записана в виде р + q/r, где р; ^ и г суть многочле ны, и г взаимно просты и степень q меньше, чем степень г. У многочлен^ р всегда имеется конечный интеграл, поэтому правило интегрирования сумм: справедливо для Д (ж) = р(а) и 力(£)= ^(ж)/г(х). Поэтому задача интегрир0. вания / сводится к задаче интегрирования р (которая очень проста) и к задая( интегрирования подходящей рациональной функции q/r.
Наивный метод состоит в следующем: если многочлен т разлагается на линейные множители, так что
т(с) = П(ж -仰)『2=1
то мы можем разложить q/r на элементарные дроби
q(a) = Q МА
Г⑺一 J (e -出尸’
где bi — многочлены степени меньше, чем щ. Эти многочлены можно разделить на а — а% и получить следующее разложение:
з т(а)з т(а)一士幺(e 一出》'一士幺(e 一出》'
з т(а)
з т(а)
一士幺(e 一出》'
一士幺(e 一出》'
где bij — константы. Это разложение можно проинтегрировать и получить
пп п*
£ 加 J 】og("出)一 £ £
i=l0=1 )=2
Таким образом, мы доказали, что каждая рациональная функция имеет интеграл, который можно выразить в виде суммы рациональной функции и логарифмов рациональных функций с постоянными коэффициентами; это значит, что такой интеграл принадлежит полю С(ж, 1пж). При таком алгоритме требуется полностью разложить на множители многочлен г, что не всегда возможно без пополнения С некоторыми алгебраическими величинами. Манипулировать такими алгебраическими расширениями часто бывает очень трудно. Даже если алгебраические расширения не потребуются, очень трудоемко разлагать на множители многочлен г высокой степени. Кроме того, данный алгоритм требует громоздкого разложения на элементарные дроби.
В методе Эрмита мы определяем рациональную часть интеграла от рациональной функции, не вводя никаких новых алгебраических величин. Подобным образом, этот метод находит производную суммы логарифмов, которая тоже является рациональной функцией с коэффициентами из того же поля. Мы видели, что если некий множитель знаменателя г входит в него в степени п, то в знаменатель интеграла этот множитель входит в степени п — 1. Значит, разложение знаменателя не должно содержать квадратов сомножителей.
л0ЖИМ, что существует свободное от квадратов разложение г Поэтому 11Pq Тогда т\ взаимно просты; и мы можем построить разложение виДа 听金;蒜 функции на простые дроби:
раq(x) _ q(c) _ 才仇(с)
祠 一 Иm) 一у頂e),
слагаемого правой части есть интеграл, и поэтому применимо пра- у ка'、;те「рИрован11я суммы; достаточно проинтегрировать каждое слагаемое посереди- Интегрирование дает
,qi(x}dx = _ (砲&/(— 1)) + / 佐a + d(%b/(E — 1))厶亚,
а и 匕 удовлетворяют соотношению а% + bdri/dx = 1. С ледов атель но, мы сХгли уменьшить показатель степени множителя т〃. Продолжим делать это, □ока этот показатель не станет единицей, и тогда оставшийся интеграл станет суммой логарифмов. Метод Эрмита очень подходит для вычислений вручную. Его недостаток состоит в том, что он требует использования нескольких дополнительных подпрограмм, а это существенно осложняет программирование.
Метод Горовица состоит в следующем: целью, как и прежде, является возможность залисать f(q(x)/r(x))dx в виде 仇/门 + /伍2〃2川0 где оставшийся интеграл после его взятия дает только сумму логарифмов. Мы знаем, что у знаменателя т】те же самые множители, что и у г, только они входят в на единицу меньших степенях, что в т? нет повторяющихся множителей и что все эти множители — те же, нто в г. Получаем:门=НОД(г, dr/dx) и 丁2 де
42/^2 записано в приведенной42/^2 записано в приведеннойлит г/НОД(г,dr/dx). Мы можем полагать, что форме, и поэтому『2 = г/НОД(г, dr/dx). Тогда
42/^2 записано в приведенной
42/^2 записано в приведенной
仇dri仇driт; dxт; dx+竺7'2+竺7'29(с) _ d (q、、、q» _ \ dq、 т(а) dx〈Q 丿 丁2 7*1 dx
仇dri
仇dri
т; dx
т; dx
+竺7'2
+竺7'2
_ r2dqx/dx -仇5 + q2Tl
「“е s = (丁2^『工/"化)/丁1 (здесь деление без остатка). Тогда получаем
~取 9? s7 г и『2 известны, а 仇 и 该 нужно отыскать. Поскольку степени много- нленов Qi и Q2 меньше, чем степени тип многочленов q^ и q? соответственно, запишем
91 (ж) = £ a^\ 92 (c) = £ 加炉.г~0г=0
И? образом, уравнение для q можно переписать в виде системы т + п нмх уравнений для п + т неизвестных. Более того, эту систему можно 'Щить и свести интегрирование (по крайней мере для этой подзадачи) к аане линейной алгебры.
Опишем теперь метод логарифмических слагаемых. Два описанных в&ц^ метода позволяют свести интегрирование произвольной рациональной фу^к ции к интегрированию некой рациональной функции (скажем, q/т), Интегру которой состоит лишь из суммы логарифмов. Мы знаем, что этот интегр^
можно вычислить, полностью разложив на множители ее знаменатель, но Эт0 не всегда бывает необходимо для выражения результатов. На самом деле з& дача состоит в том, чтобы найти этот интеграл, не используя никаких других алгебраических чисел, кроме тех, что нужны для выражения результата. Пусть
——выражение для этого интеграла, в котором в правой части фигурирует наименьшее возможное число алгебраических расширений. Коэффициенты с「 это константы, и в общем случае 也一рациональные функции. Поскольку ln(a/Z>) = 1па —1п&, мы можем без потери общности предположить, что и,являются многочленами. Далее мы можем выполнить разложение на множители, свободное от квадратов, что не добавляет никаких алгебраических расширений, и применить тождество
1п[[р:三 £°1п&.
В силу тождества
с * ln(p * q) + d * ln(p * т)三(с + d) * In р + с* In ^ + </* In г
мы можем считать, нто 出 взаимно просты, по-прежнему сохраняя минимальность алгебраических расширений. Кроме того, мы можем считать, что все & различны. Дифференцируя наш интеграл, находим
q(z)—大 с^ dvjт(а) 4^ Ui dx
Из предположения о том, что з не содержат квадратов, следует, что ни одно слагаемое в этой сумме не может быть упрощено, а из предположения о том, что Vi взаимно просты, следует, что никакое сокращение этих дробей невозможно. Отсюда вытекает, что 叫 должны быть в точности множителями многочлена г, т. е. что г (ж) = 口之1 曾£(化)・ Обозначим &勿= 口/卢色 Uj. Затем мы можем продифференцировать произведение 齡厶 откуда получим г = ^ Uidvi/dx. Эти два выражения для q и для dr/dx позволяют вывести следующий результат:
^fe(^) = НОД(0,1^) = НОД (q - '^CiUidvi/dx.Vk^=НОД(д - CkUkdvk/dx,vk).
поскольку все другие щ делятся на и^.
Теперь рассмотрим алгебраические решения дифференциального уравнения первого порядка
~ + f(x)y = д(х).
классклассу(х) = z(z) expу(х) = z(z) expнас к алгоритму Рит,а. Мы ставим задачу найти такой алго- 9To пркв。又"% дЛЯ данных / ид, принадлежащих к классу функций А, либо ритм, к°^Р кЦИЮ у, принадлежащую к данному классу В, либо доказыва- нахоД0Т ЗУ В не содержит функций, удовлетворяющих данному уравнению, ет, ит° %0Tbi рассмотрим случай, когда В всегда является классом функций, Для 口Р° относительно класса А. Чтобы решить это дифференциальное ^ненХ сделаем подстановку
класс
класс
у(х) = z(z) exp
у(х) = z(z) exp
9To приводит к решению
?(с) = ехр (一 / f(s)ds) / (g⑶卜xp / /⑴dt) tfe).
Вообще говоря, этот метод алгоритмически неудовлетворителен для нахож- тт_ния у так как алгоритм интегрирования, описанный в последнем разделе, сводит задачу нахождения этого интеграла к решению того самого дифференциального уравнения, с которого мы начинали. Риш [42] нашел метод решения таких уравнений для случая, когда А является полем рациональных функций или алгебраическим расширением некого поля, над которым эта задача разрешима. Данную задачу можно сформулировать следующим образом: для двух данных рациональных функций /ид найти рациональную функцию у, такую, что dy/dx + /(z)y = g(z)> или доказать, что таковой не существует. Функция / удовлетворяет условию, гласящему, что ехр (/” f(s)ds) не является рациональной функцией, и ее интеграл не является суммой логарифмов с рациональными коэффициентами. Задача решается в два этапа: сведение ее к чисто полиномиальной задаче и затем решение этой задачи. Алгоритм Риша рекурсивен. Прежде чем применять его, нужно (в принципе) проверить, что различные переменные, являющиеся алгебраическими расширениями, алгебраически независимы. Для рациональных функций алгоритм Риша совпадает ° алгоритмом для метода Горовица. Дальнейшие подробности, касающиеся алгоритма Риша и его расширений, можно найти в литературе [42], [33], [11].
2JL Коммутативность и некоммутативность
В
компьютерной алгебре обычно предполагается коммутативность символов.
ие математические структуры некоммутативны. Здесь мы обсудим неко- °Р*е из этих структур.
'помним, что ассоциативная алгебра — это векторное пространство V над вкоторое удовлетворяет аксиомам кольца таким образом, что сложение
с *, КОльце является сложением в векторном пространстве и выполняется тор' * В)~ (с * А) * В — А * (с * В) для всех А, В € V и с € F. Кроме Мбр;;"Раведлив закон ассоциативности, т. е. А * (В * С) = (А * В) * С. При- ассоциативной алгебры служит множество (п х п)-матриц над полем
вещественных или комплексных чисел с матричным умножением в кане^т операции *. Тогда в общем случае имеем'夾
А В В * А.
Другой важный пример некоммутативной структуры ——это алгебра Ли. дЛге^_ ра Ли определяется следующим образом: любое векторное пространство £ 后" полем F с операцией Е х Е т Е, определенной соотношением网
(©у) т阮世
и называемой коммутатором векторов z и у, называется алгеброй Ли над полем F, если оно удовлетворяет следующим аксиомам:
(L1) Операция взятия коммутатора билинейна.
(L2) [ж, ж] = 0 для всех х £ L.
(L3) [ж, [у, z]] + [% [z, с]] + [z」c, у]] =0, x,y,z Е L.
Простой пример алгебры Ли ——это векторное произведение в векторном пространстве R3.
Замечание. Связь между ассоциативной алгеброй и алгеброй Ли такова: пусть А, В — элементы некой ассоциативной: алгебры. Определим коммутатор следующим образом:
[Л, В] := Л * В — В * А.
Легко доказать, что так определенный коммутатор удовлетворяет приведенным выше аксиомам. Таким образом, мы построили алгебру Ли из ассоциативной алгебры.
Другой пример некоммутативной структуры — это кватернионы. Кватернионы имеют матричное представление.
2.12. Тензорное произведение и кронекерово произведение
Пусть V и W ——векторные пространства над полем F. Определим тензорное произведение [29] элементов V и W. Значение этого произведения должно лежать в некотором векторном пространстве. Если обозначить через &区仅 тензорное произведение элементов v £ V и w € РИ, то должны выполняться следующие соотношения: если &i,&2 € V и w € W, то
(廿[+ &2)区仅 =⑵1区仅 + ⑵2区仅.
Если wi, W2 EW n & W К to
V ®(W1 + 仅2)= & ㊂仅 1 + f 0 W2.
Если с W F, то
(e * ")包仅=e * (&区仅)=f 0(c * 仅).
Построим теперь такое произведение и докажем его разнообразные свойства.
Пусть U, V и V7 ——векторные пространства над полем F. Под билинейным отображением
g:V xW^U
отображение, которое каждой паре элементов (&,тг), где & W U понимаем соответствие элемент g(v,w) пространства U, обладающее 乙 W ставил n j
а Дующими свойствами:
⑪叫Уо & G V отображение w т ^(f, w) пространства W в простран-
口 ля ^eilHO, и для каждого w eW отображение & т g(&,^) пространства ство U лиН ; ту линейно. Доказательства нижеследующих теорем прнво- в прос'Р
дятса в [29].
а Пусть V, U — конечномерные векторные пространства над полем гуеСфема- ^ -конечномерное пространство Т над полем F и билинейное 'т 蕊V xW обозначаемое формулой
удовлетворяющие следующим свойствам.
/ Если U — векторное пространство над F ug :VxW^U является билинейным отображением, то существует единственное линейное отображение
: Т Т U)
такое, что для всех пар ®,仅),где v eV u w Е W, выполняется
g®,仅)=g*® 区仅).
Если {&i,・・・,&冷} — зто базис пространства V, a {wi,... ,wm} — базис пространства W, то элементы
fj 0 Wj? ? = 1, . . . , П U j — 1, . . . ,771
образуют базис пространства Т.
Пространство Т называется тензорным произведением пространств V nW
и обозначается V 0 W. Его размерность задается формулой
dim” 0 W) = (dim V)(dim W).
Элемент f 0 w, ассоциированный с парой (f, w), также называется тензорным произведением элементов & и 仅.
UacTo нам будет нужно находить тензорное произведение более чем двух пространств. Такое произведение ассоциативно.
Теорема. Пусть U}匕 W — конечномерные векторные пространства над F. °2°° существует единственный изоморфизм
U ^(V ^W) (U 0V)^W,
m咻о仇Что
и 0 (f 0 w) н-> (и 0 f) 0 w
ecex ^еи; V eV uw EW.
Эта теорема позволяет записывать без скобок тензорное произведение не- н^Д F "X сомножителей. Поэтому если %,... % суть векторные пространства ‘ Mbi можем образовать их тензорное произведение
力区眩区…区%
и тензорное произведение
5 В &2 冬•…& %
элементов &点 W %• Приведенные выше теоремы дают общие полезные свойств
тензорного произведения.
рассмат、рассмат、Теперь введем кронекерово произведение двух матриц. Его можно ривать как реалпзатцно тензорного произведения.
рассмат、
рассмат、
(р х 7)-матрица. х (п7)]-матрица,(р х 7)-матрица. х (п7)]-матрица,Определение. Пусть А есть (т х п)-матрица, а В есть Тогда кронекерово произведение матриц А и В —это [(тир) определенная формулой
(р х 7)-матрица. х (п7)]-матрица,
(р х 7)-матрица. х (п7)]-матрица,
幺集B :=幺集B :=/ А11В&21В/ А11В&21В@228■ • ■a271b@228■ • ■a271b@77126• • • ^mn в /@77126• • • ^mn в /
幺集B :=
幺集B :=
/ А11В
&21В
/ А11В
&21В
@228■ • ■a271b
@228■ • ■a271b
@77126• • • ^mn в /
@77126• • • ^mn в /
Иногда кронекерово произведение называют также прямым или тензорным произведением. Очевидно, что(А^В)0С^А0С + В^С.где А и В — матрицы одного размера. АналогичноА0(В^С)=А0В + А^)Сугде В п С одного размера. Кроме того, имеем(А ㊂ В)区。=4 区(В + С).Иногда кронекерово произведение называют также прямым или тензорным произведением. Очевидно, что(А^В)0С^А0С + В^С.где А и В — матрицы одного размера. АналогичноА0(В^С)=А0В + А^)Сугде В п С одного размера. Кроме того, имеем(А ㊂ В)区。=4 区(В + С).произведениемпроизведениемПример. ПустьПример. Пусть
Иногда кронекерово произведение называют также прямым или тензорным произведением. Очевидно, что
(А^В)0С^А0С + В^С.
где А и В — матрицы одного размера. Аналогично
А0(В^С)=А0В + А^)Су
где В п С одного размера. Кроме того, имеем
(А ㊂ В)区。=4 区(В + С).
Иногда кронекерово произведение называют также прямым или тензорным произведением. Очевидно, что
(А^В)0С^А0С + В^С.
где А и В — матрицы одного размера. Аналогично
А0(В^С)=А0В + А^)Су
где В п С одного размера. Кроме того, имеем
(А ㊂ В)区。=4 区(В + С).
произведением
произведением
Пример. Пусть
Пример. Пусть
4=( О4=( О31318=(;8=(;-11-11
4=( О
4=( О
3
1
3
1
8=(;
8=(;
-1
1
-1
1
Тогда
/
0
-2
0
-3
\
/
0
0
-2
-3
—2
2
-3
3
, В 0 А —
0
0
0
-1
0
0
0
-1
—2
-3
2
3
0
0
-1
1
丿
1
0
-1
0
1
4绥В =
Мы видим, что А0 В ^ В 0 А.
Пример. Пусть
е】=(jе】=(jе2 =(;е2 =(;
е】=(j
е】=(j
е2 =(;
е2 =(;
Тогда
ei 0 eiei 0 ei/ 1、оо\°// 1、оо\°/,ei 0 е2,ei 0 е2( о、1О\°/( о、1О\°/,е2 0ei,е2 0ei/ 0 \О1V// 0 \О1V/,в2 绥 @2 =,в2 绥 @2 =/ 0 \ОО\ 1 // 0 \ОО\ 1 /
ei 0 ei
ei 0 ei
/ 1、о
о
\°/
/ 1、о
о
\°/
,ei 0 е2
,ei 0 е2
( о、1
О
\°/
( о、1
О
\°/
,е2 0ei
,е2 0ei
/ 0 \
О
1
V/
/ 0 \
О
1
V/
,в2 绥 @2 =
,в2 绥 @2 =
/ 0 \
ОО\ 1 /
/ 0 \
ОО\ 1 /
гр今)есть стандартный базис в R2. Мы видим, что
но что/
0^^ '{ei 0еъ ei 0 e2, e2 0eb e2 0 e2}
есть стандартный базис в R4-
2 ]3. Внешнее произведение
*внешнее произведение (называемое также альтернированным
Введем J^h篁6乂 или грассмановым произведением). Пусть V — конечномерное произве с нств0 над R, г — целое число > 1, а V⑺—множество всех ве®;黑 а〃хнн г элементов из V, т.е. V^ = V xV х ... xV. Элемент из 獰胃кнм образом, есть кортеж (vb...vr), где все Vi € V. Пусть [/-другое ” g4HOMepHoe векторное пространство над R, и пусть f : V xV х ... xV U к°^ МуЛЬтилинейное отображение пространства V ^ U. линейное по каждой кошюненте. Иначе говоря, для каждого 5 = 1,,т имеем
/(V] ‘Vf + v;,... ~г)=/(", . . . , V^, . . . , vr) + /(V1,... , v^,... , v「), /(Vl,C*V"Vr) = C*/(V1,Vr)
ДЛЯ всех Vi, v< € V и c e R.
Вудем называть мультилинейное отображение / альтернированным^ если оно удовлетворяет условию
/(vb , vr) = О
всякий раз, когда два соседних компонента равны, т. е. всякий раз, когда существует такой индекс j < г, что V/ = vj+i. Заметим, что условия, которым удовлетворяют такие мультилинейные отображения, аналогичны свойствам определителей. Следующая теорема охватывает альтернированные произведения общего вида.
Теорема. Пусть V — конечномерное векторное пространство над F размерности п, а г — целое число, 1 < г < п. Существуют конечномерное пространство над F, обозначаемое Лг V, и мулътилинейное альтернированное отображение V⑺ т /^V; обозначаемое
(ui,... ,ur) i-> ux Л ... Л ur, У^°^летворяющие следующим свойствам.
丄 Ec^u U — векторное пространство над F 篁 g : V^ т U есть мульти- Линейное альтернированное отображение, то существует единственное инейное отображение
g* : ЛГУ т U,
"а寓。已,气mo для всех Ui,... , ur € V выполняется
2 E”v,..2 E”v,..g(iib ・ 一,%) = g*(ui Л …Л*.
2 E”v,..
2 E”v,..
,v^} — это базис пространства 匕 то набор элементов
{v% Л ... Л Vir}31 < г^ < ... < ir < п.
兄8心temc兄 базисом пространства ЛГУ.
Поэтому если {vi,... , vn} — базис пространства V, то каждый элемент д^у обладает единственным представлением в виде линейной комбинации
£ Gi..%Vh Л ... Л Vjr,力 1 V ♦ ♦ ♦ <,т
где суммирование производится по всем кортежам (£匕...,5「) целых чисел 0T 1 до п, удовлетворяющих условию
Можно также упростить обозначения, записав (г) = (?i,... , ir). Тогда выписанная выше сумма примет вид
〉:c(q v^i Л ….Л v^r *
(О
Введенная выше структура называется также грассмановой алгеброй.
Размерность векторного пространства ЛГУ дается формулой
Пример. Рассмотрим векторное пространство R4 со стандартным базисом
@1,@2,@3,%•
Тогда базис для два-форм будет таков:
ei Л е2, ех Л e3j ех Л е4, е2 Л е3? е2 Ле4, е3 Л е4.
Базис для три-форм дается выражениями
ei Л е2 Л е3, ех Л е2 Л е% ех Л е3 Л е4, е2 Л е3 Л е4.
Базис для четыре-форм состоит из одного-единственного элемента, а именно ei Л е2 Л е3 Л е4.
Определитель квадратной матрицы можно вычислить с помощью внешнего произведения. Рассмотрим (4 х 4)-матрицу
1О1О1О1О1О31О3521О521О2 \3 О7丿2 \3 О7丿Тогда/ 1 \О\°/Тогда/ 1 \О\°// 2 \1О\з// 2 \1О\з/ЛЛ/ 5 \21к°// 5 \21к°/ЛЛ0= 24ех Л е2 Л е3 Л е4,70= 24ех Л е2 Л е3 Л е4,7
1
О
1
О
1
О
1
О
1
О
3
1
О
3
5
2
1
О
5
2
1
О
2 \
3 О
7丿
2 \
3 О
7丿
Тогда
/ 1 \О
\°/
Тогда
/ 1 \О
\°/
/ 2 \1О\з/
/ 2 \1О\з/
Л
Л
/ 5 \2
1
к°/
/ 5 \2
1
к°/
Л
Л
0= 24ех Л е2 Л е3 Л е4,
7
0= 24ех Л е2 Л е3 Л е4,
7
где ei, е2, е3, ©4 — стандартный базис пространства R4. Следовательно, определитель этой матрицы равен 24. В гл. 8 мы приведем реализацию этого (очевидно, что небыстрого) способа вычисления определителя.
теперь некоторые практические методы вычисления определи- рассмо『Р:м 口р^мен笈емы直 метод обычно зависит от типа матрицы — явля- те〃幺 матР11 囁рой или символьной.
ется ли она н
Числовая матрица
прои:прои:• тпз丄 4— матрица размера п х п. Определитель матрицы А —это сумма 口уст:епенИ^ элементов главной диагонали матрицы, полученной переста-
прои:
прои:
-'„ столбцов матрицы А, взятая по всем возможным перестановкам. "°вк° с которым каждое из таких слагаемых входит в эту сумму, являет- ^Наоложительным или отрицательным в зависимости от того, является ли соответствующая перестановка четной или нечетной.
с Очевидный случай — матрица, состоящая только из одного элемента, и
в этом случае мы определим
det(A) = ац при п — 1.
На самом деле никто не вычисляет определители матриц большего размера, чем 4x4, выписывая все перестановки столбцов и находя произведения диагональных элементов. Более эффективный способ вычисления определителей основан на следующих фактах:
——Добавление одной строки (или столбца) матрицы А, умноженной на произвольное число, к другим ее строкам (или столбцам) не меняет det
—Если матрица В получена из матрицы Л перестановкой двух ее строк (или двух столбцов), то det В = — det А. Если две строки (или столбца) матрицы А пропорциональны друг другу, то det А = 0.
СимвольнаяОпределитель тода Леверъе.СимвольнаяОпределитель тода Леверъе.Идея метода состоит в том, чтобы преобразовать матрицу А с помощью двух описанных выше операций к треугольному виду, т. е. к такой форме, в которой все элементы, расположенные ниже главной диагонали, равны нулю. Из определения ясно, что определитель треугольной матрицы равен произведению элементов главной диагонали.
Символьная
Определитель тода Леверъе.
Символьная
Определитель тода Леверъе.
матрица
символьной матрицы лучше всего вычислять с помощью ме-
^рактеристический многочлен (п х п)-матрицы А — это многочлен степени п относительно А. Его можно записать в виде
Р(Х) = det(A/ —⑷=* —- с2f -…一财一卩-Cn.
зложмм теперь метод Леверье нахождения коэффициентов с% данного Многочлена. Этот метод совершенно нечувствителен к индивидуальным особенностям матрицы А. Он имеет то дополнительное преимущество, что матрица, обратная к Л, если таковая существует, также вычисляется в про- "бссе определения коэффициентов q. Очевидно, что заодно мы находим искомый определитель. Коэффициенты q многочлена Р(А) находятся вы-
числением следа каждой из матриц Bi, Вг,... , Вп, построенных СЛе щим образом. Положим 8】=А и вычислим q = tr(Bi). Затем най^е『蚊
В而=4马-1 一 必一1/), ck - Q^ ^(Bk), A; = 2,3,... ,n.
Матрицу, обратную к невырожденной матрице А, можно найти из соот шения'
4T4T(B*i — сп^1),(B*i — сп^1),
4T
4T
(B*i — сп^1),
(B*i — сп^1),
а определитель матрицы Л равен сп, если п четно, и —с^, если п нечетно
Глава 3
Системы компьютерной алгебры
3・1・ Введение
” главе мы рассмотрим некоторые системы компьютерной алгебры и опием ряд их приложений. Ныне имеется много систем компьютерной алгебры, ито мы сосредоточимся лишь на немногих из них. Системы REDUCE и 叢ЮМ основаны на языке Лисп. С другой стороны, MATHEMATICA, MAPLE и MuPAD основаны на языке С. Кроме символьных вычислений, все эти систе- МЬ1 способны проводить числовые вычисления. Это не только интерактивные среды, действующие в ответ на вводимые команды, но и языки программирования.
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. Это программная система общего назначения и язык, пригодные для символьных, числовых и графических вычислений.
“PAD — символьно-вычислительная система компьютерной алгебры, раз- танная отделом математики университета Paderborn [39]. Эта система поз- ;т вам определять свои собственные типы данных.
Мимо описанных выше систем компьютерной алгебры доступны и другие евосходные системы:
В 乎YMA —— продукт фирмы Macsyma, Inc. Она основана на языке Лисп. ней реализуется тест Аслаксена для функций комплексной переменной.
口 Дробное описание системы Macsyma можно найти в книге Дэвенпорта и
DERIVE — небольшая, но мощная система компьютерной алгебры фИп Software Warehouse, Hawaii. Она также основана на Лиспе. В системе D® • ® для решения большого круга математических задач применяются борм "& из алгебры, тригонометрии, математического анализа и матричной ал;:& ры. Эта система содержит и графические возможности.、
MAGMA (фирма Computational Algebra Group, School for Mathematics a Statistics, University of Sydney)——это система для вычислений в компьют ной алгебре, в области геометрических и комбинаторных задач, таких как группы, кольца, алгебры, модули, графы и коды.
МАТНСAD — продукт фирмы MathSoft, Cambridge, Massachussetts, Пред назначенный инженерам, научным работникам и исследователям для обрм ботки, проведения и документирования символьных и численных вычислений.
Имеются также пакеты для специальных задач в таких областях, как теория чисел, теория графов и алгебра. Интернет-сайт фирмы International School for Scientific Computing (см. предисловие) позволяет выйти на другие сайты, где можно найти дополнительную информацию о пакетах компьютерной алгебры.
32 REDUCE
Основные операции
Мы приведем краткое описание команд, чаще всего используемых в системе REDUCE. REDUCE не различает заглавные и строчные буквы. Так, записи sin(x), Sin(x) и SIN(X) означают одну и ту же команду.
Команды, чаще всего используемые в настоящей книге,——это дифференцирование и интегрирование. Чтобы продифференцировать ж3 +2ж по ж, запишем
df(х**3+2*х,х);
На выходе получим 3*х^2 + 2. Чтобы проинтегрировать ж2 + 1? пишем
int(х**2 + 1,х);
Выходом служит х*(х*2 + 3)/3. И **, и 宀 обозначают операцию возведения в степень.
Команда solve () решает ряд алгебраических уравнений, систем алгебраических уравнений и трансцендентных уравнений. Например, команда
solve(х**2 + (а+1)*х + а=0,х);
выдает решения х = -1 и х = -а.
Другая важная команда —это команда подстановки sub(). Например, команда
sub(х=2,х*у+х**2);
выдает результат 2*у + 4.
Среди прочих REDUCE включает следующие математические функции: sqrt(x) (квадратный корень, g), ехр(х) (показательная функция, exp(z)),
— нь1й логарифм, ln(c)) и тригонометрические функции sin(x), 10g(x) (иа5х) с аргументами, выраженными в радианах.
cOs(^^ tan REDUCE зарезервированы выражения i (или I) для представ- В c^^l^Qi (или PI, р工,Pi) Для числа 7Г. Так, ввод
леняя Р
дает-1,аввод
sin(pi);
к получению результата 0. Другие предопределенные константы — приведетinity, где Т означает «истина», a nil — «ложь».
ЭТ°Для дифференцирования REDUCE предлагает два способа реализации.
?^ первый способ, мы объявляем функцию, которую нужно продиффе- аТЬ как operator. В следующем примере показано, как пользоваться 祟;м способом. Цифра 2 в df () указывает, что мы дифференцируем дважды.
operator f,
f(х) := х*х + sin(x);
result := df(f(x),x,2);
На выходе получаем result := -sin(x) + 2
С другой стороны, мы также можем объявить, что f зависит от х, и затем продифференцировать f по х.
depend f, х;
f := х*х + sin(x);
result :二 df(f(x),x,2);
В системе REDUCE тип цифровых данных по умолчанию — это рациональные числа. Команда
+ 0.1 + 1/3;
“аст на выходе
73/30
Переключатель on rounded позволяет проводить вычисления с вещественными ^сламн. Команды
Qn rounded;
+ 0.1 + 1/3;
■Wt на выходе
2 43333333333
Reduce
известны лишь самые элементарные тождества, вроде
cos(x) 01cos(x) 01CQ$(-X) sin(pi) l?g(e) ”(i*pi/2)
cos(x) 0
1
cos(x) 0
1
Пользователи могут, применяя команду LET, добавлять другие правила v щения вырсокений, вроде тригонометрического тождестваР"
for all х let sin(x)"2 + cos(x)"2 = 1;
тогда ввод выражения
sin(y)c2 + cos(y)"2 - 5;
даст на выходе
-4
REDUCE [17]REDUCE [17]Другие команды можно найти в руководстве для пользователей
REDUCE [17]
REDUCE [17]
3・2・2. Пример
В качестве примера покажем, как из псевдосферических поверхностей можно вывести солитонные уравнения. Мы покажем вывод уравнения синус Гордона Обобщения на другие солитонные уравнения очевидны. Солитонные уравнения можно описать посредством псевдосферических поверхностей, т. е. поверхностей с постоянной отрицательной гауссовой кривизной. Примером служит уравнение синус Гордона
Покажем, как можно использовать систему REDUCE для вывода уравнения синус Гордона из элемента длины такой поверхности. Поле метрического тензора задается выражением
g = dxi(8)d出工 + cos(u(出i,出2))d出i(8)dx^
+ cos(uQi,出2)川出2 & dx± + dx2 <8)由⑵ т. e. элемент длины есть
Здесь 观一это гладкая функция переменных 出i н 出2 • Сначала нужно вычислить скалярную риманову кривизну R из д. Тогда мы получим уравнение синус Гордона, наложив условие
К = -2.
Вычисление скалярной кривизны хорошо описано во многих учебниках (см., например, [49]). Для полноты приведем соответствующие формулы. Имеем
gu = g22 = 1, gi2 = ^21 = cos(u(a;i,a;2)).
Метрический тензор д можно записать в матричной форме
\ 921§22 丿
ТогДа величина,ТогДа величина,обратная g, задается выражениемобратная g, задается выражением9 -(产9 -(产产 产产 产гдегде11„22 一m2 —_g丄=9— 9- - 2”SID U11„22 一m2 —_g丄=9— 9- - 2”SID Ugi2 = g2igi2 = g2iCOSU sinuCOSU sinu
ТогДа величина,
ТогДа величина,
обратная g, задается выражением
обратная g, задается выражением
9 -(产
9 -(产
产 产
产 产
где
где
11„22 一m2 —_
g丄=9— 9- - 2
”SID U
11„22 一m2 —_
g丄=9— 9- - 2
”SID U
gi2 = g2i
gi2 = g2i
COSU sinu
COSU sinu
b вычислим символы Кристоффеля. Они определяются как
Г爲п := }д°Ь (дЬт,т1 + g&%m — 9тпп,Ь)->использовано правило суммирования по повторяющимся индексам и— Ндьт
9bm,l9ьm:2 := -£2。
Затем нам нужно вычислить тензор римановой кривизны, который дается выражением
or —「г _「г I рг рп _ рг рп
±bmsq ' — mq,sms,7 1 ns mg 人ms”
Отсюда выводится тензор Риччи
Rmq • — Rmag - ~^mqa
T. e. тензор Риччи строится посредством свертки. Из Rnq мы получим 兄器 с помощью соотношения 尺器:=gmnRnq. Наконец, скалярная кривизна R дается формулой R :二 R窮.С помощью выписанного выше поля метрического тензора находим, что
RR2 a2u sin w 3xi 3x2 '2 a2u sin w 3xi 3x2 '
R
R
2 a2u sin w 3xi 3x2 '
2 a2u sin w 3xi 3x2 '
Если R = —2, то получаем уравнение синус Гордона.
Мы применяем концепцию операторов. Операторы — наиболее общий тип объектов, доступных в системе REDUCE. Обычно они содержат параметры, и их можно параметризовать совершенно произвольным образом. В объявлении оператора описывается лишь идентификатор оператора. Число параметров не ^является. Операторы представляют математические операции или функ- 閣”’Мея объявляем и как оператор, и он зависит от х. Сам параметр х также ^является как оператор, и он зависит от 1 и 2. Поскольку в результате на- вычислений появляются члены вида cos2(ж) и sin2(ж), нам необходимо
,ключить тождество cos2 (ж) + sin2(x) = 1 для упрощения выражений.
% tensor.red
:at「ixg(2,2);
atrix gl(2 94. 7二
arra s и,,,, А матрица, обратная к g;
gamaa(2,2,2); array R(2,2,2,2); array Ricci(232);
operator u x.
g(l,l) := 1; g(2,2) := 1;
g(l,2) := cos(u(x(l),x(2))); g(2,l) := cos(u(x(l),x(2)));
gl := g*(-1);% вычисление обратной матрицы
for a := 1:2 do
for m := 1:2 do
for n := 1:2 do
ganuna(a,m,n) := (1/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 ''ganunaC', a, " , " 5m," ," ,n, ")=" , gannna(a,m,ii);
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)*ganuna(ii,m,s));
cos(u(x(l),x(2)))**2 := 1 - sin(u(x(l),x(2)))**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,",u,q,") = ", Ricci(m,q);
array Riccil(2,2);
for m := 1:2 do
for q := 1:2 do
Riccii(m,q) := (for b := 1:2 sum gl(m,b)*Ricci(q,b));
CS := for m := 1:2 sum Riccil(m,m);
Вычисление скалярной кривизны исходя из поля метрического тензора (например, метрики Геделя, метрики Шварцшильда) — это одно из самых первых применений компьютерной алгебры. Здесь мы показали, что такие вычисления можно обобщить для нахождения солитонных уравнений. Модифицировав
тензора, можно получить другие солитонные уравнения.
g(l,2) •g(l,2) •=cos(u(x(l),Х(2))); g(2,l) := cos(u(x(l),х(2)));
g(l,2) •
g(l,2) •
泳НО заменить на
м°" .二 cosh(u(x(l),х(2))); g(2,l) := cosh(u(x(l),х(2)));
нужно ввести правило тождественного преобразования Пополнительно ну
Т .址(u(x(i),х(2)))**2 := cosh(u(x(i),х(2)))**2 - 1;
прощения выражения.
炉 л Р ество прикладных программ, написанных в системе REDUCE, мож- 】айтн в [11], [19], [49], [50], [51]. В [49] описаны приложения к квантовой н。 V。R [5Q1 даны приложения к дифференциальным уравнениям. В [521 механике,口 l。
ссмаТриваются приложения к нелинейным динамическим системам.
3.3. MAPLE
Основные операции
В системе MAPLE различаются строчные и прописные буквы. Команда Sin(0.1) выдаст sin(. 1), тогда как sin(0.1) выдаст желаемый результат 0.09983341665,
Команда дифференцирования в MAPLE — это diff (). Ввод
diff(х*3 + 2*х,х);
приведет к результату Зх"2 + 2. Команда интегрирования — это int (). Ввод int(x"2 + 1,х);
Дает хл3/3 + х.
В MAPLE есть две разных команды для решения уравнений. Команда solve(x^2 + (1+а)*х + а=0,х);
решает уравнение х*2 + (1+а)х + а = 0 и выдает результат х = Тих = -а. Команда,
^е^^^УРавнение хл2 - х-1.0 = 0и выдает результаты -0.6180339887 и口口2 подстановки имеет вид subs(). Например, команда^е^^^УРавнение хл2 - х-1.0 = 0и выдает результаты -0.6180339887 и口口2 подстановки имеет вид subs(). Например, командаfsolve(x"2 - х - 1=о,х)
^е^^^УРавнение хл2 - х-1.0 = 0и выдает результаты -0.6180339887 и口口2 подстановки имеет вид subs(). Например, команда
^е^^^УРавнение хл2 - х-1.0 = 0и выдает результаты -0.6180339887 и口口2 подстановки имеет вид subs(). Например, команда
subs(x=2,x*y + х~2);
脸т 2у + я с *
$qrt( )/ п^Очих MAFLE включает следующие математические функции: ^(х;(н::^атн?й корень,,^), ехр(х) (показательная функция, е”), cos(x) атУР^льный логарифм, 1п(а;)) и тригонометрические функции sin(x), ''©(х), аргументы которых выражаются в радианах.
1548
Предопределенные константы в MAPLE исчерпываются следующИми.
Catalan, Е, Pi, false, gamma, infinity, true.
Прочие команды можно найти в руководстве пользователя к системе MAP^r
Пример
В качестве примера рассмотрим одну квантовомеханическую задачу. ДЛя 晓 данной пробной функции одномерного потенциала найдем приближенное зн; чение энергии основного состояния. Уравнение на собственные значения в 0A номерном пространстве имеет вид
冇2 л2
-而姦+ "(“)〃(“)= Ей(吟
Мы будем применять вариационный принцип для вычисления энергии основного состояния частицы в поле с потенциалом
/ сх для
[оо для а; < О,
где с > 0. Для такого потенциала энергетический спектр дискретен и ограничен снизу. Возьмем
/、 f zexp(—для z > 0,
{0 для г<0
в качестве пробной функцииt здесь а > 0. Нужно помнить, что пробная функция пока не нормирована. Из уравнения на собственные значения находим, что математическое ожидание энергии дается выражением
(и\Н\и) _ /о 出© °宓(-爆刍"+ с出)呪 °”"出 _ Зс Я2а2 他同店3出2 eхр(-2a出川出2a + 2m '
где 〈 I 〉 означает скалярное произведение в гильбертовом пространстве 乙2(0,8)・ Это математическое ожидание имеет минимум при
В приведенной ниже программе мы вычисляем (Е) и затем определяем минимум (Е) при варьировании а. Итак, энергия основного состояния больше ин* равна
9 (2%2c2:/з
4 ( 3£ 丿•
energy.map
потенциал
:= с*х;
#叩' u :' # УР: Hu ::#叩' u :' # УР: Hu ::.обная響鸞
#叩' u :' # УР: Hu ::
#叩' u :' # УР: Hu ::
,火aXD(~a*X丿 >
:хНа собственные значения
'3sli幫;/(2*m)*diff (u,x,x) + V*u;
ование для нахождения среднего значения :^^ненормированного
1 .二 int(u*Hu,х), resl •
“ мммирование коэффициентов экспонент R := collect(resl,exp);
й подстановка граничных условий о := 0 - subs(x=0,res2);
нахождение нормы и для нормировки res4 := int(u*u,x);
res5 := -subs(x=0,res4);
нормированное среднее значение ехре := res3/res5;
нахождение минимума по а
minim := diff(ехре,а);
res6 := solve(minim=0,а);
а := res6[1];
приближенное значение энергии основного состояния
appgse = subs(а=0,ехре);
Замечание. В данном случае годится лишь вещественное решение уравнении res6; а именно res6 [1].
34 AXIOM
341. Основные операции
кс^^М делает УП0Р на строгую проверку типов. В отличие от других систем те「к теРН0^ алгебры, типы в AXIOM не фиксированы, а являются динамиКОм объектами. Они создаются в процессе работы программы в ответ на Пов / д 1 пользователя. Типы в AXIOM варьируются от алгебраических ти- (HanD Р^мер, многочлены, матрицы и степенные ряды) до структур данных осмыСЛе Р' СПиски, словари и входные файлы). Типы можно комбинировать мноГоч ННЬ1М образом. Мы можем строить многочлены от матриц, матрицы Чам^ к Нов от степенных рядов, таблицы хеширования с символьными клю- ""ементамн в виде рациональных функций и так далее.
Категории в 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.
использует D для дифференцирования выражения
f .= exp exp х
D(f,x)
й третий аргумент и в D инструктирует AXIOM взять n-ю про- деобязательн ^咿乂叩 D (f , х, 3).
“sW^^OM имеется много библиотечных подпрограмм для интегрирования.
Например,
даетдаетintegrate((х**2+2*х+1)/((х+1)**6+1),х)
дает
дает
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)
sin(2*x) == 2*sin(x)*cos(x)
cos(2*x) == cos(x)**2 - sin(x)**2
Поэтому команда
sinCosExpandRules (sin(a+2*b+c))
применяет описанные выше правила.
Многие другие команды можно найти в [24].
3.4.2. Пример
Прн решении систем полиномиальных уравнений центральную роль играет базис Гребнера [12], [24], [34]. Если многочлены
{рг : i — 1, . . . ,п}
гдегдеполностью обращаются в нуль, это делает и их комбинация
где
где
№ои^^""Ш1'”"нт" " в общем случае тоже являются многочленами. Всевоз- ЛОм 工° комбинации этого вида порождают пространство, называемое идеа- лине^^ ‘ порождаемый семейством многочленов, состоит из множества всех мп Си 1Х комбинаций этих многочленов с полиномиальными коэффициента- Нера (с^Ма образующих (или базис) G для идеала I является базисом Греб- иДеала /1°СИТельно П0РяДка <),если в результате любой редукции элемента / Нуль. 9T РеДуцированному многочлену (относительно G) всегда получается 5е Лекси "OP'WK может быть лексикографическим (имеется в виду, что по- СТеПеням К°г^аФического упорядочения переменных следует упорядочение по ПеРеменных), обратным лексикографическим (сначала упорядочение
лает AXIOM.DMP означает команды:лает AXIOM.DMP означает команды:переменных в обратном лексикографическом порядке, затем упорядочен^ степеням), общим степенным (сначала все упорядочивается по степеням 110 тем в лексикографическом порядке) и обратным общим степенным (сна;'*' все упорядочивается по степеням, затем в обратном лексикографическое ^" рядке). Большинство систем компьютерной алгебры способны находить 6a^“ Гребнера для данной системы многочленов. Здесь мы рассмотрим, как это 近
лает AXIOM.
DMP означает команды:
лает 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
"274537 -
1264
г5 +
6 4丄
荻‘+
182 о
一竺 610
+
103 — 2745^"
2857
10980'
у2
丄 112 6
84
—305
d +
1264 4 前r出-
工з 549
84 2
+蒞"
+
1772
冇①+
2 2745?
29 6 + /_
17 4
-X .
16
_u
г3 + ~-х
32
215
+丁
1
+ 4'
3・5・ MATHEMATICA
Основные операции
Система MATHEMATICA различает заглавные и строчные буквы. Команда sin[0.1] для вычисления синуса от 0.1 выдаст сообщение об ошибке: «возможно, ошибочное написание», тогда как команда Sin[0.1] выдаст правильный ответ 0.0998334.
В системе MATHEMATICA команда дифференцирования
D [х"3 + 2*х,х]
выдает 2 + 3*хЛ2. Команда
Integrate[хЛ2 + 1,х]
выдает х + х^З/З.
Команда Solve [ ] может решать ряд алгебраических уравнений, систем алгебраических уравнений и трансцендентных уравнений. Например, команд"
Solve[х*2 + (а + 1)*х + а ==0,х]
выдаст решения х = -1 и х 二-а.
Оператор замены /. применяет правила к выражениям. Рассмотрим вьФа жение
х*у + х*х
[огаа комара
х*у + х*х /,х -> 2
пРЗультат 4 + 2*у.
вь1даст рс 丿 чИХ MATHEMATICA включает следующие математические
СРе^м s rt [х] (квадратный корень, «), Ехр[х] (показательная функция, функции^ /натуральный логарифм, 1п(х)) и тригонометрические функции 済),Log^s g Тап[х] , аргументы которых выражаются в радианах. 51ППХедопределенные константы представлены следующим списком:
工 Infinity, Pi, Degree, GoldenRatio, E, EulerGanuna, Catalan
команды можно найти в руководстве пользователя к системе МАТНЕ-
MATICA [62].
Пример
В качестве примера рассмотрим спиновые матрицы для спина 1
/0/^0、/ 000 \
“ I 0 0\/^九 I j s~ := I о о I •
\ 0 00 /\ 0 Eh о 丿
Вычислим коммутатор двух этих матриц
[§+,S_] := 5+5— — £_£十
и затем найдем собственные значения этого коммутатора. Программа в системе MATHEMATICA будет следующей:
(* spin.m *)
sp = « 0, Sqrt[2]*hb3 0}, {0, 0, Sqrt [2] *hb}, {0, 0, 0}} sm = {{0, 0, 0}, {Sqrt[2]*hb, 0, 0}, {0, Sqrt[2]*hb, 0}} comm = sp . sm - sm . sp Eigenvalues[comm]
На выходе получим
「22
{。, -2 hb , 2 hb }
3・6・ MuPAD
J Основные операции
MuPAD — с
зом в ун истема компьютерной алгебры, которая разработана главным обра- фической ''"тете в Падерборне. Она является символьной, численной и гра- Р^пелива^стем。宜.MuPAD предоставляет пользователю возможность распа- 口瓦侬丁 oge;' ИНстРукций. По синктасису MuPAD близок системе MAPLE и В система м'^Ррментрованнке возможности, схожие с системой AXIOM.
AD прописные и заглавные буквы в инструкциях различаются.
Инструкция Sin(0.1) выдает в качестве результата Sin(0.1), тогда как инструкция sin(O.l) дает требуемый результат 0.09983341664. В MuPAD диф^ ференцирование производится с помощью инструкции diff (). Введя
diff(хЛ3 + 2*х,х),
получим на выходе
2
х + 2
Команда интегрирования — int(). Введя
int(хЛ2 + 1,х),
получим
3
■ X
X +——
3
Командой
solve(x"2 + (1+а)*х + а=0,х);
решают уравнение х^2 + (1+а)*х + а=0, что дает в качестве результата {-а, -1}. Подстановка выполняется командой subs(). Например, выполнив
subs(х*у + х"2,х=2),
получим 2 у + 4. Среди прочих, MuPAD включает следующие математические функции: sqrt(х) (квадратный корень), ехр(х) (экспоненциальная функция,七④),1п(х) (натуральный логарифм) и тригонометрические функции sin(x), cos(x) , tan(x) с аргументами в радианах.
В MuPADe имеются встроенные константы
PI, Е, EULER, FALSE, gamma, infinity
3.6.2. Пример
В качестве примера рассмотрим, как метод Пикара будет аппроксимировать решение дифференциального уравнения dy/dx = /(/, у) с начальным условием у(/0) = уО, где / — аналитическая функция относительно хну. Интегрируя обе стороны уравнения, получаем
и(0)=Уо+ [ f(s,y(s))ds.
Теперь начиная с у。эта формула может использоваться для последовательной аппроксимации точного решения, если процесс будет сходиться. Следующее приближение решения задается формулой
Уп-w^ = Уо +『($)叭s))ds.
Для примера аппроксимируем решение уравнения dy/dx = х + у^ используя пять шагов метода Пикара. Для ввода из файла используется команда read(f ilena^e^ - В нашем случае эта команда выглядит так: read ("picaTd11) • на выходе получим ,
3456
2
XXXX
x + x +
■— — + — — + — —一一— + ]
31260720
/*picard*/
х0:=0;/* начальное х*/
уО:=1;/* начальное у*/
у:=уО:
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,yi),s),s=x)-subs(int(f(s,yl),s),s=xO)); yl:=subs(y,x=s);
end__f or;
print(y);
Глава 4
Объектно-ориентированное программирование
В этой главе мы обсудим основные понятия объектно-ориентированного программирования .Мы начнем с определений объектов, классов и абстрактных типов данных. Далее рассмотрим такие концепции, как передача сообщений, наследование, полиморфизм и другие. Они будут пояснены множеством примеров. В разд. 4.5 мы опишем объектно-ориентированные языки C++ и Java. Наконец, завершим главу кратким введением в объектно-ориентированные языки Eiffel, Smalltalk и Oberon.
По прочтении этой главы читатели будут ознакомлены с важнейшими концепциями, применяемыми в объектно-ориентированных системах. В последующих главах мы увидим, чем эти концепции полезны для разработки систем компьютерной алгебры.
Объекты, классы и абстрактные типы данных
Объекты
Любая вещь, существующая на свете, — это объект. Цветы, автомобили, матрицы —все это примеры объектов. Автомобили характеризуются размером, цветом и другими признаками. Им присущи операции, вроде запуска двигателя, включения фар, нажима на педаль тормоза и так далее. Подобным образом, у матриц есть такие характеристики, как число строк, столбцов и т. д. Им присущи операции вроде сложения, умножения, взятия тензорного произведения и так далее.
В этой главе мы изучим применение объектно-ориентированных методов (ООМ) моделирования мира. Как указывает амбициозное название ООМ, объекты играют важную роль. Объект в компьютере обладает уникальным идентификатором (ID), не зависящим от значений его атрибутов. ID используется для того, чтобы отличить один объект от другого. Он не может измениться на протяжении всего времени жизни объекта. На рис. 4.1 показаны основные составляющие объекта.
Внутренняя структура объекта состоит из двух частей: атрибутов (данных) и методов (операций).
Атрибуты описывают свойства объекта в конкретный момент времени. Это говорит нам кое-что об объекте, но не о том, как данный объект действу-
ID
Методы внешнего интерфейса
Внутренние методы
Атрибуты
рис. 4.1. Схематическая диаграмма объекта.
ет. Их можно считать локальными данными, связанными с данным объектом. Например, у кота есть вес, порода, пол и возраст. У многочлена есть степень, число членов и коэффициенты при каждом члене.
Атрибуты, которые мы задаем для объекта, зависят от потребностей решаемой задачи. Иногда нам не нужно помнить, что у кота четыре лапы, или же мы можем просто считать это известным заранее. Иногда структура матрицы бывает важна; скажем, является ли она разреженной или плотной? На самом деле решения о том, что включать в набор атрибутов объекта, это вопрос про-
ектирования системы.
Методы описывают поведение, свойственное объекту, и то, что этот объект может делать или сделает, когда что-нибудь случится. Например, у работников есть имя, возраст, пол, заработная плата и табельный номер. Однако одних атрибутов недостаточно,чтобы полностью описать работников. Нам нужно знать также, какие операции применимы к работникам. Эти операции могут включать повышение в должности, увеличение заработной платы, получение разрешения на пользование автомобилем и так далее. Они представляют собой действият которые могут быть выполнены объектом или над объектом. В результате таких действий состояние объекта может измениться.
На самом деле есть два вида методов. Первый из них — это методы внешнего Интерфейса, обеспечивающие способ общаться с объектом. Второй вид— э?° внутренние методы. Они задают поведение объекта, но недоступны извне екта. Другими словами, их достаточно знать лишь проектировщику объек- ^- ^Пользователям объекта достаточно знать лишь о методах внешнего интер-
Метод, свойственный некому объекту, активируется сообщением, послан- го 迎У。11" объектом тому объекту, который содержит этот метод. Кроме то'°" м°жет быть вызван другим методом, пр ин адлежащим тому же объекту, в“севом локального сообщения. Контракт устанавливается между вызы- 呢屯 *Им объектом и вызванным объектом. Этот механизм называется переда- Р^Л^'б吗‘ни疝 между объектами, и он будет подробнее обсужден в следующем
Рис. 4.2. Схематическая диаграмма класса Matrix.
В качестве примера рассмотрим объект, представляющий класс Matrix, показанный на рис. 4.2. У него есть уникальный идентификатор (ID), отличающий его от всех иных объектов. Этот объект содержит информацию вроде числа строк/столбцов, значений каждого элемента и т. д. Он также содержит некоторые методы внешнего интерфейса, применяемые для обработки информации, такие, как inverse, determinant и т. д. Известно, что для нахождения обратной матрицы нужно проверить, не равен ли нулю определитель этой матрицы. Значит, метод inverse должен содержать следующие внутренние методы:
check_determinant проверить, равен ли нулю определитель матрицы f ind_inverse найти обратную матрицу.
Заметим, что методы check-determinant и f ind_inverse не видны снаружи. Только методы, принадлежащие к классу Matrix, имеют к ним доступ. Эта концепция скрытия данных важна. Она способствует модульности и уменьшает эффекты интерференции при разработке класса, проявляющиеся тогда, когда изменения в одном модуле требуют внесения изменений также и в другие модули [63]. ■
В объектно-ориентированных системах эффекты интерференции вызываются изменениями лишь в интерфейсе объекта, а не изменениями во внутренних деталях. Чтобы свести эффекты интерференции к минимуму, интерфейс объекта должен сохраняться как можно более простым и скрывать от пользователей как можно больше подробностей.
1.2. Абстрактные типы данных
Атрибут может быть простым типом вроде целого числа, буквы или же другим объектом. Объект, составленный из других объектов, известен как аггрегация. Такая возможность построения объектов обеспечивается абстрактными типами данных (АТД), в которых объекты строятся из существующих структур данных. АТД — это набор данных и множество операций с этими данными. Он скрывает внутреннюю информацию, но обеспечивает пользователям открытый набор методов манипуляции с этим объектом. Поэтому другие точно знают, какие операции АТД может выполнять. Однако они не знают, в каком вИде эти данные хранятся и как производятся эти операции. Это очень мощная методология, поддерживающая модульность и снижающая общую сложность программных систем.
Ни один объект не может непосредственно считывать или изменять состояние никакого другого объекта. Доступ к атрибутам другого объекта или их изменение выполняется посредством методов его внешнего интерфейса. 0rдна- к0 такой доступ возможен, лишь если сам объект это разрешает. Это свойство называется инкапсуляцией. Именно этим способом объектно-ориентированная система осуществляет скрытие информации. Скрытие информации включает скрьттие как самих данных, так и реализации операций. Инкапсуляция собирает вместе данные и функции, работающие с этими данными. Таким образом, этот набор данных и функций можно рассматривать как нечто единое. Эти свойства приводят к слабой взаимозависимости между объектами.
Рнс. 4.3.Рнс. 4.3.Рассмотрим, например, раздатчик напитков, изображенный на рис. 4.3. Он принимает на входе горячую воду и выдает на выходе либо чай, либо кофе в зависимости от того, какая кнопка нажата. Это устройство подобно абстракт-
Рнс. 4.3.
Рнс. 4.3.
Кофе Чай
Схематическая диаграмма раздатчика напитков.
ному типу данных. Горячая вода, чай и кофейный порошок подобны набору данных, а операции ——розливу чая или кофе. Устройство рассматривается как черный ящик. Нас не интересуют подробности того, как именно осуществляются операции. То, чего мы хотим, —это чтобы устройство наливало нужный нам напиток.
Заметим, что внутренние механизмы заключены в металлический кожух. Отверстия в кожухе предназначены лишь для входа (горячей воды) и выхода (чая или кофе). Таким образом, внутренние механизмы не только скрыты от глаз пользователей, но и физически недоступны им. Более того, механизм 04 ной операции скрыт от другой операции и недоступен ей. Поэтому мы можем усовершенствовать операцию размешивания кофейного порошка, не затрагивая операции заваривания чая. Мы можем также добавить еще одну операцию, например разлив горячего шоколадного молока, не затрагивая двух исходных операций. Вот так абстрагирование и скрытие информации работают в этом примере.
Классы
Много схожих между собой объектов можно охарактеризовать одним и тем же общим описанием. Возьмем, например, четное число и простое число. Их можно описать более общей абстракцией — Integer. У них есть много общих свойств и способов действия. Их можно расставлять в фиксированном порядке, складывать, умножать и так далее (точное определение целого числа см. в гл. 2). Собрание экземпляров объектов, обладающих этими свойствами, можно обозначить как Integer.
Объекты проверяют, сравнивая их с общими понятиями, чтобы выяснить, можно ли рассматривать данный объект как Integer. Эти соображения приводят к понятию класса. Описание объектов, имеющих сходные свойства, называется классом. Каждый объект является экземпляром некого класса. Класс состоит из структуры данных и операций, применимых к данному классу. Он также полезен для классификации объектов и обеспечивает основу для создания нового объекта, служащего экземпляром этого класса.
В объектно-ориентированном мире в каждом классе есть три разных вида атрибутов [25]:
Атрибуты класса описывают свойства класса как целого. Это могут быть конкретные числовые значения или средние величины, связанные с классом. Например, рассмотрим класс Card, являющийся надклассом всех карточных игр, продающихся в отделе универмага, таких, как карты для покера, для пасьянса, для гадания и другое. Атрибутами этого класса могут быть общее число имеющихся в продаже колод карт, число различных типов колод или средняя цена колоды карт.
Общие атрибуты экземпляров описывают общие свойства, присущие всем экземплярам этого класса. Рассмотрим, например, подкласс Poker_card. Общим атрибутом экземпляров может быть число карт в каждой колоде, или же размер каждой карты, скажем, 6 см х 9 см. Иногда общие атрибуты
экземпляровэкземпляровописывают характеристики набораописывают характеристики набораназывают переменными класса. Они отличаются от атрибутов
экземпляров
экземпляров
описывают характеристики набора
описывают характеристики набора
класса, определенных выше, которые
объектов-экземпляров класса как целого.
Атрибута экземпляров описывают атрибуты каждого объекта. Хотя на- 3’ звания этих атрибутов для данного класса заданы, их значения приписы
ваются лишь индивиду сильным объектам. В классе Poker_card могут быть следующие атрибуты экземпляров:
масть ранг цветмасть ранг цветнапример, бубны, черви и т. д., например, валет, король, туз и т. д., например, черный или красный.
масть ранг цвет
масть ранг цвет
Передача сообщений
Объекты с атрибутами и методами общаются между собой посредством nepeda- чи сообщений. Сообщение содержит имя, присвоенное методу внутри объекта. qh0 Может также содержать несколько аргументов. Для передачи сообщений нужно две стороны: отправитель и получатель. Когда объект получает сообщение, активируется связанный с ним метод. После завершения работы этого метода объект-получатель возвращает результат объекту-отправителю.
Предположим, что служащий некой компании по имени Джон устраивает вечеринку, чтобы отпраздновать дни рождения всех служащих этой компании, родившихся в январе. Чтобы найти дни рождения (ДР) всех служащих компании, он должен запросить информацию из их личных дел. В этом процессе объект, называемый «Джон», отправляет сообщение Запрос_ДР каждому объекту из класса Служащие. ДР каждого служащего затем возвращается в качестве результата. На рис. 4.4 изображена эта ситуация.
Объект 'служащийОбъект 'служащий
Объект 'служащий
Объект 'служащий
Р"& 4.4. Обмен сообщениями между объектами (ДР — дата рождения).
В Данном примере сообщение исходит от метода, принадлежащего объекту 4°н»・ Адресат сообщения — другой объект, называемый служащий. Название сообщения — это Запрос_ДР, а его действие состоит в том, чтобы получить ДР объект служащий.
出е "а самом деле объекты разных классов могут реагировать на одно и то ис°°бщение по-разному. Это один из видов полиморфизма, о котором речь “"ет позже в этой главе.
Наследование
Наследование — это мощная концепция объектно-ориентированных систем Она чаще всего используется в качестве механизма создания специальных 冃己 риантов некого более общего класса. Это называется обобщенной специал^^ цией. Она применяется к семействам классов, имеющих похожие, но не совп; дающие атрибуты и методы. В такой ситуации полезно наследование свойств" Оно обеспечивается наличием нескольких уровней абстракции, причем ка也 дый уровень представляет различные варианты или уточнения некого класса Более низкие уровни строятся из более ранних. Другими словами, надкласс является обобщением подклассов. Аналогично, подкласс данного класса пред, ставляет специализацию этого класса, относящегося к более высокому уровню Наследование также обеспечивает механизм управления классами и совместного использования общих кодов.
В качестве примера рассмотрим обратимые матрицы, нижнетреугольные матрицы и симметричные матрицы. У них есть некоторые общие свойства, скажем, число строк, столбцов и так далее. Эти общие свойства могут быть унаследованы от более общего класса, называемого Матрица. На рис. 4.5 показана иерархия наследования. Подклассы наследуют свойства от надкласса Матрица. Подклассы являются специализированными вариантами этого надкласса.
Рис. 4.5. Иерархия наследование класса.
Надкласс Матрица может также наследовать свойства от некоторых надклассов ? вроде Математическая Структура, который в свою очередь может наследовать свойства от класса, называемого Структура. Таким образом можно построить иерархию наследования.
Различные объектно-ориентированные языки могут поддерживать разные виды наследования. Smalltalk подцерживает лишь простое наследование. Другие языки, скажем C++, поддерживают множественное наследование. Оно позволяет классу наследовать свойства более чем от одного надкласса-предк^. Например, обратимая симметричная матрица является одновременно обратимой матрицей и симметричной матрицей, и здесь имеет место множественное наследование, как показано на рис. 46 Всегда требуется, чтобы правила, определяющие множественное наследование, были способны разрешить любое противоречие, которое может возникнуть, скажем конфликт названий, когда одно и то же название используется для двух разных надклассов и наследуется
рис-рис-4.6. Пример4.6. Примермножественного наследования.множественного наследования.
рис-
рис-
4.6. Пример
4.6. Пример
множественного наследования.
множественного наследования.
и тем же подклассом. Отсюда ясно, что хотя множественное наследование —это мощный механизм, он увеличивает сложность.
Положение станет еще сложнее, если Обратимая и Симметричная наследуют от одного и того же над-надкласса Матрица, как показано на рис. 4.7. Подкласс
Обратимая Симметричная Матрица потенциально может наследовать свойства от одного над-надкласса дважды. Эта ситуация называется повторное насле
дование.
Р”с. 4.7. Пример повторного наследования.Р”с. 4.7. Пример повторного наследования.
Р”с. 4.7. Пример повторного наследования.
Р”с. 4.7. Пример повторного наследования.
Есть также другая форма наследования, называемая выборочным наследованием- В этом случае подкласс наследует лишь некоторые свойства от над- ^^сса. Это делает часть информации недоступной некоторым подклассам.
ПРо“ав:ПРо“ав:11PHMер,база данных компании может состоять из нескольких частей, до- либо менеджеру, либо бухгалтеру, либо продавцу. Продавец имеет Ронь П к записям 0 клиентах, которые недоступны бухгалтеру. С другой стоМе '& бухгалтер имеет доступ к записям о продажах, недоступным продавцу. то;е“*ер имеет доступ ко всей информации, хранящейся в базе данных, в - 'исле к записям о персонале компании, недоступным ни бухгалтеру, ни
ПРо“ав:
ПРо“ав:
;"У・ 9та ситуация изображена на рис. 4.8.
Подклассы наследуют методы и атрибуты от надклассов. Однако подкласс может переопределить любой из методов или атрибутов, унаследованных от надкласса, а также определить дополнительно свои собственные атрибуты и методы. Это чрезвычайно мощное средство, особенно в сочетании с полиморфизмом. Эта концепция будет рассмотрена в следующем разделе настоящей главы.
Иногда бывает полезно ввести классы на неком уровне, не существующие в реальности, но являющиеся удобными понятийными конструкциями. Такие классы называются абстрактными классами. Абстрактный класс обычно занимает подходящее положение в иерархии классов. Он определяет формы поведения своих наследников, но не их фактическую реализацию. Назначение этих абстрактных классов — определять, что должно быть сделано, но не как эти задачи должны решаться. Это дело наследников — обеспечивать реализацию предписанных форм поведения. В эти абстрактные классы не входят никакие экземпляры, созданные ими самими. Напротив, экземпляры создаются подклассами этих абстрактных классов, и эти экземпляры соответствуют некоторым объектам реального мира.
Библиотеки классов, содержащие много классов, обычно связаны отношениями наследования. Наследование снижает избыточность и дублирование программ. Это одно из основных преимуществ использования данной концепции. Классы организованы в иерархии, причем более общие классы находятся на верхних уровнях иерархии, а более специализированные классы располагаются ниже.
4 Полиморфизм
зово полиморфизм означает «имеющий много форм». Это просто Гре^ес qro 05ъекТы-адресаты (получатели) могут оказаться относящимися бо- зна"”т‘ к 04HoMу типу. Отправитель сообщения не обязан знать, к какому клас- лее че^адЛежит получатель. Он лишь запрашивает некое событие с помощью сУ ПР” сообщений, а получатель знает, что ему делать. Это позволяет объек- 口ере"типов (получателям) различными способами реагировать на одно
там 限е с00бщение. В результате программы становятся более компактными, и т0 т才小 пх легче понять. Существует три вида полиморфизма: а значих,
]Полиморфизм включения.
Операционный полиморфизм.
Параметрический полиморфизм.
.4」・ Полиморфизм включения
Традиционные языки программирования требуют от программистов, чтобы те точно определяли, какой метод будет применяться в процессе вычисления. Зтот подход называется статическим связыванием. Полиморфизм включения позволяет принимать такие решения лишь во время работы программы, что называется динамическим связыванием (или поздним связыванием). С помощью динамического связывания система обеспечивает, что нужный метод будет вызван для нужного класса в ходе работы программы. Хотя динамическое связывание гибко, оно снижает производительность программ. Это происходит из-за того, что теперь в ходе работы программы нужно запускать алгоритм просмотра и подбора. К счастью, эти затраты в большинстве языков минимальны. Статическое связывание, с другой стороны, надежнее и эффективнее.
Этот вид полиморфизма применим к языкам программирования со строгим контролем типов, вроде языков Eiffel, Simula и C++, и он связан с наследова- нием,Это средство, обеспечивающее общий интерфейс для разных классов в одном и том же дереве наследования с помощью одного и того же названия метода. Однако каждый класс может реализовывать этот метод по-своему.
Рассмотрим дерево наследования, изображенное на рис. 4.9. Круг и ^Р^угольник — это подклассы абстрактного надкласса Фигура. Пусть метод ьгчисление_пло1цади возвращает площадь геометрической фигуры. Метод сление_площади определен в классе Фигура. Однако никакой метод для вычисления площади фигуры неприменим без явного определения того, к му виду фигур она относится. Подклассы наследуют этот абстрактный "и переписывают его определение посредством подходящих алгоритмов то СЛения площади круга или треугольника. Иначе говоря, когда одно и 6ь1ть с°°6щениб посылается всем классам, реакция каждого из них может с °т“инной от реакции другого. Это свойство позволяет легко обращаться р^口е;Р°"ными объектами. Если у нас есть много разных фигур, вроде па- °「раммов, ромбов, прямоугольников и так далее, мы можем разослать
Рис. 4.9. Пример полиморфизма включением.Рис. 4.9. Пример полиморфизма включением.
Рис. 4.9. Пример полиморфизма включением.
Рис. 4.9. Пример полиморфизма включением.
одно сообщение Вычисление_площади и ожидать, что каждый из них возвратит правильный ответ. Нам не нужно знать наперед, какая у каждой из них форма.
Этот вид полиморфизма позволяет обойтись без некоторых процедур проверки типа в языках со строгими типами. Это также делает разработку программ проще и динамичнее.
Операхщонный полиморфизм
Этот вид полиморфизма применим к одному и тому же методу с несколькими определениями. Решение о том, какое именно определение будет использовано, принимается исходя из типа параметров, содержащихся в сообщении. Для примера рассмотрим следующее определение класса на языке C++, которое называется Document:
class Document
void print(char* s);
void print(char c);
void print(int n);
};
В этом классе один и тот же метод print определен трижды. Каждый вари^ ант отличается типом входных данных. Поэтому когда объекту типа Document посылается некое сообщение, будет вызван тот или иной вариант метода prin, в зависимости от типа данных параметров сообщения. Это называется п^ грузкой функции.
На самом деле этот вид полиморфизма давно используется для арифмети^ ческих операций в большинстве языков. Операции +, -, * и / могут использоваться для сложения, вычитания, умножения и деления как целых чисел, т&к
плавающей точкой. Компьютер автоматически генерирует подходя- Н ^^^щинный код на основании типа операндов (целые числа или числа с 喘盤ей *й).
4・4・3・Этот-4・4・3・Этот-Параметрический полиморфизм
4・4・3・
Этот-
4・4・3・
Этот-
вид полиморфизма использует типы в качестве параметра в объявлении Например, пусть в стеке хранятся целые числа. Для языков со стро- к"®с^нтролем типов, вроде Eiffel, Java и C++, при изменении типа данных, '"М м чтобы хранить в стеке строки, нужно будет переписать программу. ска\ ' говоря, придется переписывать программу для каждого нового типа “° тх что сделало бы работу программиста слишком трудоемкой и подвер- *енно员 ошибкам. Эту трудность можно преодолеть с помощью концепции параметрического полиморфизма.
Р Параметрический полиморфизм обычно применяется для такой реализа- цин структур данных и алгоритмов, которая была бы в значительной степени независима от типа объектов, с которыми они работают. Например, параме-
трический Stack может описывать реализацию структуры Stack, содержащую объекты произвольного типа. Тогда объект Stack можно будет использовать дЛЯ хранения строк, целых чисел, указателей и так далее. Компилятор автоматически будет генерировать коды для этих классов Stack. Другой хороший кандидат для применения параметрического полиморфизма — структура Vector. В принципе тип данных, содержащихся в структуре вектора, слабо зависит от того, какие операции можно совершать над векторами. Алгоритм сортировки —еще один идеальный кандидат на параметрический полиморфизм. Обычно нас интересует только правильность сортировки элементов данных, а не типы данных этих элементов, лишь бы их можно было расположить в подходящем порядке. Короче говоря, использование разными типами данных одних и тех же программ ——самое важное преимущество параметрического полиморфизма.
Зти концепции реализованы в Библиотеке Стандартных Шаблонов (БСШ). Библиотека Стандартных Шаблонов включает следующие классы: deque, list, queue, set и vector. Мы подробно опишем эту библиотеку в гл. 5.
Пока что мы ввели главные понятия объектно-ориентированного програм- ^ирования, такие, как объект, класс, абстрактный тип данных, передача со- 现 нии,наследование и полиморфизм. В следующем разделе мы обсудим Достоинства наиболее популярных ныне объектно-ориентированных языков ор^еРаммирования. Мы опишем, как перечисленные выше понятия объектно- ированного программирования реализуются в этих языках.
4・5. Объектно-ориентированные языки
3"ес;°"а次© имеется много объектно-ориентированных языков. Мы обсудим °6*ье "'которые из них и сравним их с C++. Чаще всего используемые н De] ^°'°Риентированные языки — это C++, Java, Smalltalk, Eiffel, Оберон
В начале 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+十, но все конструкции, которые его разработчики сочли ненадежными (вроде указателей и ссылок для основных типов данных), из него устранены.
Язык С++
С тех пор как язык C++ был впервые изобретен, он претерпел три существенные ревизии, каждая из которых добавляла к нему новые конструкции и изменяла его. Первая ревизия имела место в 1980 г., а вторая — в 1990г. Тре-
ви3иЯ осуществлялась в процессе стандартизации C++. Несколько лет *',Р°началась работа над стандартом C++. Для этой цели был создан специ- 喘近 совместный комитет ANSI и ISO (International Standards Organization). *ь" тй вариант предложенного стандарта был закончен 25 января 1994 г. В О。' варианте комитет ANSI/ISO по C++ сохранил возможности, определен- ?Т°Мсоздателем языка Бьярном Страуструпом, и добавил несколько новых. '伍° первоначальный вариант отражал текущее состояние C++. После за- ^Т°Т ия первого варианта произошло событие, повлекшее за собой очень сев езное расширение стандарта: создание Библиотеки Стандартных Шаблонов 常ЕП)・БСШ — это набор типовых подпрограмм вроде сортировки и поиска, которые можно использовать для обработки данных. После утверждения пер- воро варианта комиссия проголосовала за включение БСШ в спецификацию C++ Добавление БСШ расширило область применения C++ за пределы его первоначального определения. Стандартизация C++ потребовала больше времени чем кто-либо ожидал в тот момент, когда эта работа начиналась. При зтом в язык было добавлено много новых возможностей и внесено множество мелких изменений. Версия C++, определенная комитетом по C++, обширнее и сложнее, чем исходная разработка Страуструпа. Однако ныне стандарт завершен. Последний вариант был одобрен комитетом 14 ноября 1997 г. В настоящей книге для реализации Символьного C++ использован Стандартный C++. Стандартный С+Н——это та версия, которая ныне принимается всеми большими компиляторами.
Поскольку основой разработки C++ явилось объектно-ориентированное программирование, важно точно определить, что это такое. Это методология, взявшая на вооружение лучшие принципы структурного программирования и соединившая их с несколькими мощными концепциями, позволяющими организовывать наши программы более эффективно. В общих чертах этот метод можно описать так: когда программирование ведется в объектно-ориентированном стиле, мы разбиваем задачу на ее составные части. Каждая часть становится отельным объектом, содержащим свои собственные инструкции и дан- ные,относящиеся к этому объекту. В результате сложность уменьшается. Все объектно-ориентированные языки обладают тремя общими свойствами: инкапсуляцией, полиморфизмом и наследованием.
Все программы состоят из двух основных компонент: операторов програм- мм (кода) и данных. Код — это та часть программы, которая выполняет дей~ Ствия, а данные — это информация, которую эти действия изменяют. Инкапсуляция — это программный механизм, соединяющий воедино код и данные, стваР"МИ к°А оперирует, и охраняющий и то и другое от внешнего вмешатель- рИ неправильного использования.
сте Ъектн°-°Риентированном языке код и данные могут быть связаны вме- g Таким образом, что возникнет относительно автономный черный яи^ик. св^з^ Я1Чика находятся все необходимые данные и код. Когда код и данные ект_ Ме^ДУ собой таким способом, создан объект. Другими словами, объ- “аннь j° Устройство, поддерживающее инкапсуляцию. Внутри объекта код, только ИЛИ и то и Другое могут быть закрытыми (private) (т. е. доступными ""я этого объекта) или открытыми (public). Код или данные, являю-
щиеся закрытыми, известны и доступны лишь другой части этого объекта 弓 означает, что закрытые код или данные недоступны никакому фрагменту 口口; граммы, не принадлежащему объекту. Если код или данные открыты, то ° ' доступны другим частям нашей программы, хотя и определены внутри 05; екта. Обычно открытые компоненты объекта используются для организаи управляемого интерфейса с закрытыми компонентами объекта.
Полиморфизм — это свойство, позволяющее использовать один и тот 改е 広冃 терфейс для целого класса действий. Специфическое действие определяется конкретной ситуацией. Рассмотрим, например, стек (т. е. очередь, организм ванную по принципу «последним обслуживается первый в очереди»). В нащ^й программе могут потребоваться три разных вида стека. Один стек используется для целых чисел, другой для чисел с плавающей запятой и еще один для литер. В этом случае алгоритм, реализующий стек, будет одним и тем же, хотя хранящиеся в нем данные различны. В языке, не поддерживающем объектно-ориентированный стиль, нам, возможно, пришлось бы создать три разных набора подпрограмм управления стеком, каждый из которых вызывался бы по своему отдельному имени и имел отдельный интерфейс. Однако благодаря полиморфизму в C++ мы можем создать один общий набор подпрограмм управления стеком (один интерфейс), работающий во всех этих трех конкретных ситуациях. Таким образом, если мы знаем, как использовать один стек, мы можем пользоваться всеми тремя. Понятие полиморфизма часто описывается выражением «один интерфейс, много методов». Это означает, что можно создать общий интерфейс для группы родственных видов деятельности. Полиморфизм помогает снизить сложность, позволяя использовать один интерфейс для задания общего класса действий. Теперь это уже дело компьютера выбрать определенную спецификацию (т. е. метод), применимый к той или иной ситуации. Первые объектно-ориентированные языки программирования были интерпретируемыми, так что полиморфизм, конечно, поддерживался в процессе работы программы. Однако C++ — это компилируемый язык. Поэтому в C++ полиморфизм под держивается и во время работы программы, и при
компиляции.
Наследование — это процесс, посредством которого один объект может приобретать свойства другого объекта. Это важно потому, что такой процесс поддерживает принцип иерархической классификации. Знания, как правило, обозримы именно из-за того, что они организованы иерархически (т. е. сверху вниз). Например, Студент является Личностью. Без использования классификации для каждого объекта пришлось бы в явном виде задавать все его характеристики. Однако с помощью наследования в объекте достаточно задавать лишь те его качества, которые отличают его от всех объектов того *е класса. Он может наследовать общие атрибуты от своего предка. Таким обр* зом, именно механизм наследования свойств позволяет объекту быть специфическим экземпляром более общего случая.
Многие конструкции C++ специально предназначены для поддержки инкапсуляции, полиморфизма и наследования. Однако мы можем писать на C++ программы любого типа, применяя самые разные подходы. То, что язык C++ поддерживает объектно-ориентированное программирование, не означает, чт°
Язык JavaЯзык Java4.52Java-4.52Java-можно писать только объектно-ориентированные программы. Как и 口抄 н°^)предшественника С, самое крупное преимущество C++ состоит в его 駕。стн.
Язык Java
Язык Java
4.52
Java-
4.52
Java-
эТ0 объектно-ориентированный язык программирования, разработан- V “ мпанией Sun. Он имеет много общих черт с языками С, C++, Objective С н" имер циклы for имеют одинаковый синтаксис во всех этих четырех язы- ^На^Но он не основан ни на одном из этих языков, и разработчики не стреми- 侬 “ сделать его совместимым с ними. Java —это язык, используемый в сети ^Хрнет. Большое влияние на него оказал язык C++. Java и C+十 используют и тот Же базовый синтаксис, и объектно-ориентированные конструкции языка Java похожи на аналогичные конструкции C++. В самом деле, если осо- 6о не присматриваться, легко спутать программу на языке Java с программой ^ С++・ Из-за их внешнего сходства часто ошибочно считают, что Java —это просто альтернатива C++. Но хотя эти языки родственны, Java и C++ были разработаны для решения разных классов задач. C++ оптимизирован для создания высокоэффективных программ. Для этой цели C++ компилирует-
ся в высокоэффективный исполняемый код. Java оптимизирован для создания переносимых программ. Для обеспечения переносимости Java компилируется в псевдокод (называемый байт-кодом Java), который обычно интерпретируется. Это делает программы на языке Java очень хорошо переносимыми, но не очень эффективными. Поэтому Java — превосходный язык для Интернет- приложений, которые должны работать на самых разных компьютерах. Но для высокоэффективных программ C++ останется наиболее предпочтительным языком.
Из-за сходства между Java и C++ большинство программистов, умеющих писать на C++, легко переходят на язык Java. Навыки и знания, приобретенные при знакомстве с C++, несложно перенести на язык Java. Однако некото- pbie важные отличия все же существуют, так что будьте внимательны, чтобы не прийти к ошибочным выводам.
Первоначально язык Java был создан потому, что C++ неадекватен для
некоторых задач. Поскольку разработчики не были связаны необходимо- стер сохранять его совместимость с существующими языками, они смогли Учесть и исправить выявленные практикой недостатки более ранних объектно- °риентированных языков. Было добавлено несколько новых возможностей, от- рь1еТВ0ВаВШИХ в C++,такие, как сборка мусора и многопотоковость. Некото- возможности C++, оказавшиеся на деле не столь полезными, как в теории, до : Мн°Жественного наследования и перегрузки операций, были отброшены.
^нх пор продолжаются споры, насколько удачен был этот выбор.
без.без.^е важнее то, что язык Java был разработан《с нуля», чтобы обеспечить Мы°^СНое выполнение программы в сети, даже если источник этой програм- нзя ненадежным и, возможно, злокозненным. Это потребовало у стране- °тсу;* некоторых возможностей языков С и C++. Наиболее примечательно теорет в*' В ЯЗЬ1ке Java указателей. Программы не могут (по крайней мере и'ески) иметь доступ к произвольному адресу в памяти.
без.
без.
Кроме того, Java была разработана не только как независимая от аппарат ной платформы система, вроде С, но и в виде скомпилированного двоичнор' кода. Поскольку это невозможно сделать для процессора произвольной ар^й тектуры, Java скомпилирована в промежуточный байт-код, интерпретируемы^ в процессе исполнения специальным интерпретатором Java. Поэтому для 口 реноса языка Java на новую платформу достаточно загрузить интерпретатор
Кроме того, язык Java был спроектирован для написания безошибочных программ. В поставляемых программах на С содержится в среднем одна ощ^ ка на 55 строк кода. Примерно половина этих ошибок связана с выделением свободной памяти и с ее освобождением. Напротив, Java обладает рядом особенностей, снижающих вероятность появления ошибок в программах:
строгий контроль типов;
отсутствие ненадежных конструкций;
язык компактен, так что им легко овладеть в совершенстве;
тексты легко писать и читать;
Java объектно-ориентирован, так что повторное использование кода не вызывает затруднений;
Java поддерживает параллельные вычисления;
в Java имеется много встроенных классов.
Синтаксис в Java намеренно сделан подобным С и C+十. Он тоже различает строчные и прописные буквы. Ниже мы опишем синтаксис программирования на языке Java.
Типы данных
Основные типы данных в языке Java очень похожи на те, что применяются в С. Добавлены булевы массивы, массивы строк и истинные массивы. Однако реализация типов данных сделана существенно более четкой в нескольких отношениях:
В С и C+十 ряд особенностей реализации зависят от платформы и компилятора (например, разрядность int). В Java все такие параметры заданы явно.
Java запрещает преобразование друг в друга произвольных переменных. Разрешены лишь преобразования числовых переменных и преобразования между классами и надклассами одного и того же объекта.
Все числовые переменные в Java имеют знак.
Ниже мы перечислим встроенные типы данных, имеющиеся в Java:
—boolean
1-бит. Может принимать только 2 значения: true и false.
true и false являются определенными константами языка. Это не то же самое, что True и False, TRUE и FALSE, нуль и не-нуль, 1 и 0 или любое другое численное значение. Булевы переменные не могут быть преобразованы ни в какой другой тип переменных, и никакая другая переменная не может быть преобразована в булеву переменную.
一 byte
байт, со знаком (дополнительный код).
Охватывает значения от —128 до 127.
一 short
байта, со знаком (дополнительный код).
Значения от -32 768 до 32 767.
一 int
байта, со знаком (дополнительный код).
От —2 147 483 648 до 2 147 483 647. Как и для всех числовых типов, переменные типа int можно преобразовывать в переменные других числовых типов (byte, short, long, float, double). Если проводятся приближенные преобразования (скажем, int в byte), то они делаются по модулю длины более «короткого» типа.
——long
байтов, со знаком (дополнительный код).
От -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807.
—float
байта, IEEE 754.
Охватывет числовой диалазон
от 1401298464324817076 - 45 до 3.40282346638528860е + 38 (положительные или отрицательные). Как и для всех числовых типов, переменные типа float могут быть преобразованы в переменные других числовых типов (byte, short, int, long, double). При приближенных преобразованиях в переменные целых типов (скажем, float в short) дробная часть отбрасывается, и преобразование делается по модулю длины более «короткого» типа.
—double
8 байтов, IEEE 754.
Охватывает диапазон значений
от 4.94065645841246544е - 324d до 1.79769313486231570е + 308с? (положительные и отрицательные).
一 char
байта без знака, Unicode.
Объекты типа char — это не то же самое, что объекты типа byte, int, short или String. Их нельзя преобразовать ни в какой другой тип, и зти типы нельзя преобразовать в тип char.
一 String
Строка (string) — это объект. Он содержит 0 или большее число знаков, заключенных в кавычки.
J array
Массивы — это объекты. Многомерные массивы создаются как массивы массивов.
Оператор sizeof не нужен в языке Java, поскольку все размеры опре- ^лены точно. Например, данные типа int всегда содержат 4 байта.
Java включает те же операторы if, else, for, while и switch., что 宣 язык С. Однако все проверки условий должны возвращать булевы знат^ ния. Поскольку операторы присваивания и арифметические операторы 耻 возвращают булевых значений, некоторые из наиболее запутанных пров& рок условий языка С в языке 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 не нужно явным образом освобождать память.
, упоминали, что в Java нет указателей. Значит ли это, что в Java нет списков? Как построить связный список без указателей? В java.util с'' са класс Vector. Он может делать все то, что может делать связный 以 меется
" к В конце концов, повторное использование кода — это одно из основ- с° свойств ООП. Все переменные объектов в Java суть ссылки, тогда как в н°|Х 近* языках они могут быть указателями. Главное различие состоит в том, ^^Г не можем производить арифметические операции со ссылками, тогда
к с указателями это возможно. Поэтому всякий раз, когда в C++ нам ну- ^^ указатель, в Java нам нужно использовать сам объект. С другой стороны, основные типы данных
byte, short, int, long, float, double, char, boolean
не являются ссылками. Если нам нужно сослаться на один из них, нам при- 附3 сначала «обернуть» этот тип данных в некий класс. Java предоставляет готовые «оберточные» классы для
Byte, Short, Integer, Long, Float, Double, Character, Boolean.
В Java есть встроенные классы String, Vector и Bitset. Кроме того, в Java есть встроенные классы Biginteger и BigDecimal для работы с большими целыми числами и с большими числами в десятичной системе. Ниже приведена программа, показывающая применение класса Biginteger.
//Verylong. j ava
import j ava.math.*;
public class Verylong
public static void main(String[] argv)
Biginteger blBiginteger b2Biginteger b3Biginteger blBiginteger b2Biginteger b3new BigInteger(M12345");
Biginteger bl
Biginteger b2
Biginteger b3
Biginteger bl
Biginteger b2
Biginteger b3
new Biglnteger("56789");
new Biginteger("-56712");
Biginteger b4 = bl.add(b2);
System.out.printin("b4 = " + b4) Biginteger b5 = bl.multiply(b3); System.out.printIn("b6 = " + b5) Biginteger b6 = b3.abs();
System.out.println("b6 = " + b6) Big工nteger b7 = bl.add(b2);
System.out.printIn("b7 = " + b7) Rouble x = bi.doubleValueO ; ystem.out.printin("x = " + x);
)
В языке Java определен один особый класс, класс Object, являющие предком всех других классов. Его можно также считать классом-контейнеп В нем описано двенадцать компонентов: один конструктор и одиннадцать 配 тодов. Четыре из них, а именно clone(), hashCode(), equals() и toStrine.<\ предназначены для того, чтобы они могли быть переопределены. Посредство^ этого они станут четкими и непротиворечивыми инструментами дупликац^^ объектов и выяснения того, когда они одинаковы. В следующей программ® показано небольшое применение класса Object.
// МуObject.java
import java.math*; // for Biginteger, BigDecimal
public class MyObject
public static void main (String args [])
Object [] a = new Object [5]
a[0] = new String ("Good Morning");
a[l] = new Integer(4567);
a[2] = new Character(JX?);
a[3] = new Double(3.14);
a[4] = new Biginteger("234567890");
for(int i=0; i<a.length; i++) {
System.out.println(a[i]);
a[3] = new BigDecimal("11.5678903452111112345");
System.out.printin(a[3]);
boolean b = a[4].equals(a[3]);
System.out.println("b = n + b);// => false
Object [] x = new Object[1];
x[0] = new Float(2.89);
boolean r = x.equals(a);
System.out,println("r = " + r);// => false
Java все передает по значению. Когда мы передаем некому методу основные типы данных, мы делаем отдельную копию соответствующего основного типа данных. Когда передаем методу дескриптор (handle), то делаем копию этого дескриптора. Поэтому все передается по значению. Это позволяет считать
■口『op чем-то вроде «объекта», поскольку дескриптор неявным образом ^©'长'едовывает его при каждом вызове метода. Это показано в следующей 罪 грамме:
〃 pass-java
imp”t java.math.*
public class Pass
{ static void inverse(int x)
x = г;
System.out.println("x = " + x);// =>-7
static void twice(Biginteger b)
Biginteger b2 = new Biginteger("2");
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
Biginteger bl = new Biginteger("ЗЗЗЗЗЗ");
twice(bl);
System.out.printin(bl);// => 333333
}
Массивы всегда передаются по ссылке. Следующая программа показывает Это 迎* основных типов данных и абстрактных типов данных.
〃 Pass1.java
Sport java.math.*;
fWc class Pas si
;tatic void inverse(int x[])
产=_x[o];
static void twice(Biginteger b[])
{
Biginteger bl = new Biginteger("2");
b[0] = bl.multiply(b[0]);
}
public static void main(String[] argv)
{
// отведение памяти для одномерного массива
// с одним элементом
int [] j = new int [1];
j[0] = 7;
inverse(j);
System,out.printin(nj [0] = " + j [0]); // => -7
Biginteger [] b = new Biginteger [1];
b[0] = new Biginteger(,1333333,,);
twice(bl);
System.out.printin("b[0] = " b[0]);// => 666666
В следующей программе мы покажем, как можно реализовать в языке Java рациональные числа. Класс Rational. java реализует рациональные числа и арифметические операции с ними: сложение (add), вычитание (substract), умножение (multiply) и деление (divide). Этот класс расширяет класс j ava. 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;
long den = this.den;
if(den < 0)
£ e - r-l)*num; den 二(-l)*den;
private void reduceO
' thi s.normas i ze ();
long g = gcd(this. num, this. den);
this.num /= g;
this.den /= g;
private long gcd(long a,long b)
{
long g;
if (b == 0)
return a;
else
g = gcd(b,(a%b));
if(g < 0) return -g; else return g;
Public long nim() { 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;
tJ1is.normalsize();
public void add(Rational r)
th}s*nuin= (this. num*r. den ()) + (r .num()*this. den) 'his.den = this.den * r.den();
‘7548
this .normalsizeO ;
public void subtract(long num,long den)
this.num = (this.num*den) - (nnm*this.den);
this.den = this.den * den;
this.normalsize();
public void subtract(Rational r)
this .num = (this. num*r. den) - (r.nmn()*th.is.den);
this. den. = this . den*r. den ();
this.normalsize();
public void multiply(long num,long den) {
this.num = (this.num*num);
this.den = (this.den*den);
this.normalsize();
public void multiply(Rational r)
this.num = (this.num*r.num());
this.den = (this.den*r.den());
this.normalsize();
public void divide(long num,long den)
this.num = (this.num*den);
this.den = (this.den*nmn);
this.normalsize();
public void divide(Rational r) {
this.num = (this,num*r.den());
this.den = (this,den*r.nmn());
this.normalsize();
public static boolean equals(Rational a,Rational b)
if((a・m
LUm()*b.den()) == (b.nmn()*.den()))
{ return else г return
true;}
false; }
}
ublic boolean equals(Object a)
' if(!(a instanceof Rational))
{ return false; }
return equals (this, (Rational) a);
public Object clone() { return new Rational(num,den); }
public String toStringO
StringBuffer buf = new StringBuffer(32); long num, den, rem;
this.reduce();
num = this. num;
den = this.den;
if (num == 0) return "0";
if (num == den) return. "I”;
if (num < 0)
{
buf.append("-n);
num 二-num;
rem = num%den;
if(num > den)
?uf.append(String.valueOf(num/den));
if(rem == 0)
Return buf.toStringO; j else
but. but. buf.but. but. buf.;uf・appand(" “);
but. but. buf.
but. but. buf.
append(String.valueOf(rem))
aPpend (*'/•');
aPpend(String.valueOf(den))
return but. toStringO ;
public float floatValue() {
return (float) ((float)this.num/(float)this.den);
public double doubleValueO {
return (double) ( (double)th-is. num/ (double)this. den);
public int intValue() {
return (int) ((int)this.num/(int)this.den);
public long longValue() {
return (long) ((long)this.num/(long)this.den);
public void print()
System. out.print (this. toStringO );
public void printlnO {
System.out.println(this. toStringO);
// метод main() используется для проверки других методов. public static void main(String args [])
{
Rational rl = new Rational(-4,6);
Rational r2 = new Rational(13,6);
rl.add(r2);
System.out .println(rl.toStringO);
Rational r3 = new Rational(123,236);
Rational r4 = new Rational(-2345,123);
r3.multiply(r4);
System.out.printIn(r3.toString());
н new Rational(3,6);
н new Rational(1,2);
版… b,二 equals(г5,г6);
booieM t.p”ntln("bl = ” +bl);
System,匕2 = equals(r4,r5);
b°°】e3ncut printin ("b2 = " +b2);
System® f
工 " = new Rational(3,17);
Ra"";]「8 = (Rational) r7. clone();
Rati°naout printin(->r8 = " + r8.toString());
T:nd main
;// end class Rational
В бедующих программах показано, как можно реализовать класс 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++)
for(i=0; j< columns; i++)
entries [i] [j] = o.o;
:atrix(int in,int nsdouble[] [] A)
E, j;
г®' = m;
Slumns = n.
『tries 二 a;
{ "ic void add(Matrix M)
• rows != M.rows) I I (this.columns != M.columns))
System.out.printin("matrices cannot be added");
System.exit(0);
int i, j;
for(i=0; Kcolumns; i++)
for(j=0; i<rows; 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.println("matrices cannot be multiplied");
System.exit(0);
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] = Matb..random() }
public boolean equals(Matrix A,Matrix B) {
int i, j;
for(i=0; j<rows; i++) {
for(j=0; j<columns; j++)
{_,. entries[i][j] != В.entries[i][j]) if return false;
Lurn true;
}
public boolean equals(Object ob) jf(!(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 String();
for(i=0; i<rows; i++)
for(j=0; j<columns; j++) {
result = result + String.valueOf(entries [i][j])+"
result = result +
result = result + "\n";
r®turn result:
Public void onStdout()
System.out.println(toString());
public static void main(String args []) M&"ix M = new Matrix(2,2);
М.entries[0][0] = 3.4; М.entries [0] [1] = 1.2;
М.entries[1] [0] = 4.5; M.entries[l] [1] = 5.8;
Matrix N = new Matrix(2,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.printin("M = \n" + M,toString());
System. out. print In ( H \n '*);
Matrix X = new Matrix(2>2);
X = M.multiply(N);
System.out .printIn("X = \n" + X.toStringO);
Matrix Y = new Matrix(2,2);
Matrix Z = new Matrix(2,2);
boolean bl = Y.equals(Z);
System.out.println(nbl = " +bl);
Z.randomize();
System.out.println(nZ = \口" +Z);
boolean b2 = Y.equals(Z);
System, out .printIn(nb2 = " +b2);
System.out.printin("\nn);
double d[] [] = new double [2] [2];
d[0] [0] = 2.1; d[0] [0] = -3.4;
d[l] [0] = 0.9; d[l][1] = 5.6;
Matrix В = new Matrix(2,2,d);
B.add(B);
System.out.printin(nB = \n” + B.toStringO);
System.out.println(n\nn);
Matrix U = (Matrix) X.clone();
System, out .print In (HU = \n" + U. toStringO); }
Чтобы проиллюстрировать различия между C++ и Java, мы представим программы построения бинарного дерева. Программа строит бинарное дерее° поиска из 100 000 вершин. Содержимое дерева обходится с помощью послед^" вательного обхода. Заметим, что элементы упорядочены в восходяхцем порядке обхода. В общем, C++ работает значительно быстрее, чем Java.
“арное аерев。C++:
〃 tree.cpP
,.nClude <iostream.h>
#inClude <stdlib.h>
class Tree
{
private• jut data;
Tree *left, *right;
public:
Tree(int);
void insert(int);
void inorder();
};
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(口 > data) {
if(right != NULL)
right~>insert(n); else
right = new Tree(n);
}
fid Tree: : inorder ()
if(this != NULL)
left->ii10rder ();
/, 3№сь печатать данные...
right~>inorcier();
}
void main()
{
Tree *root = NULL;
int i, m, n = 100000;
for (i = 0; i < n; i++)
{
m = rand();
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);
,、什 void inorder() pubL
< if(left != null) left.inorderO; 〃 здесь печатать данные...
J(right != null) right • inorder。;
public static void main(String args[])
Random : Tree t : int n = int i ; for (intRandom : Tree t : int n = int i ; for (intr 二 new Random(start);
Random : Tree t : int n = int i ; for (int
Random : Tree t : int n = int i ; for (int
=null;
i=0; i<n; i++)i=0; i<n; i++){int m = if (t == t = new else{int m = if (t == t = new else:100000;
i=0; i<n; i++)
i=0; i<n; i++)
{
int m = if (t == t = new else
{
int m = if (t == t = new else
Math, abs (r. nextInt () \%1000); null) Tree(m);
t.insert (m);
}
t.inorder();
} }} }
} }
} }
4.5.3. Другие объектно-ориентированные языки
Кроме C++ и Java, есть ряд других объектно-ориентированных языков. В настоящем разделе мы вкратце обсудим Eiffel, Smalltalk и Oberon.
Eiffel [57] 一 это чисто объектно-ориентированный язык программирования со структурой управления, аналогичной языку Паскаль. Он удовлетворяет сле- Аующим требованиям к реализации абстрактных типов данных (АТД) в языке программирования:
e ЯЗЬ1К Должен позволять определять новые, задаваемые пользователем типы Данных, т. е. обеспечивать средства построения АТД;
Он ^°лжен поддерживать создание одного или более экземпляров опреде- .ляемых пользователем типов данных, соответствующих экземплярам АТД;
в нем должны быть средства поддержки процедурных абстракций, т. е. он Должен обеспечивать процедуры и функции с подходящими механизмами .ПеРедачи параметров для реализации операций с АТД;
.азмк должен допускать реализацию общих АТД;
он "Оп^ен полностью поддерживать инкапсуляцию;
Должен поддерживать повторное использование компонент программ Оторые из предыдущих требований также способствуют этому).
Абстрактные типы данных легко реализуются в Eiffel в качестве к^胱 (соответствующих типам). Программирование в Eiffel включает тюстро©"® новых классов из существующих классов либо посредством задаваемых "电 ентом взаимоотношений, либо посредством наследования, либо обеими з^"' способами. Идея договора между компонентами программ приводит в 娇"
граммистской практике к увеличению вероятности порождения безошибо^ программ. Eiffel можно описать как язык, который:
основан на манипуляциях с объектами,
строго контролирует типы данных,
подцерживает создание безошибочных программ,
способствует легкости повторного использования кода,
основан на теории абстрактных типов данных,
позволяет строить новые типы данных из существующих,
подцерживает метод программирования, называемый программированием посредством контракта.
Объект в Eiffel является экземпляром класса, а класс ——описанием типа данных. Типичный класс состоит из описания операций, применимых к экземплярам соответствующего типа данных, вместе с описанием хранящихся данных. Поэтому нам нужно изучить следующее:
Как создавать отдельные объекты — экземпляры типа данных.
Как записывать описания классов, реализующих типы данных.
Как разрабатывать и выполнять программу на Eiffel.
Начнем с того, что покажем, как объявлять объекты (посредством объявлений) и как применять к ним операции, присущие этому типу данных (посредством инструкций). В Eiffel операции, присущие некому типу данных, реализуются как свойства (features) этого класса. Поэтому мы вкратце рассмотрим, как создаются новые классы, В последнем разделе мы объясним, как строить на языке Eiffel систему (исполнимую программу), что позволит нам заняться практическими вопросами программирования.
Типичная программа на Eiffel манипулирует объектами. Чтобы облегчить построение инструкций, определяющих, какие манипуляции должны быть осуществлены, для наименования объектов применяются идентификаторы. Например, чтобы объявить, что некий идентификатор является ссылкой на объект типа PERSON, мы пишем:
PERSON
где р является ссылкой на объект, a PERSON—имя определенного ранее типа данных. Мы будем говорить, что р относится к типу PERSON. Чтобы объявить идентификатор для очереди объектов типа PERSON, мы пишем:
QUEUE[PERSON]
где QUEUE—это обобщенный тип данных, т. е. тип данных QUEUE был реализм ван таким образом, что тип элементов, содержащихся в конкретной очереди,
задан в тот момент? когда объявляется объект «очередь». Таким обра- М0「6拉毋 нмрук口ии QUEUE [PERSON] квадратные скобки [PERSON] представля- зоМ, в к? о^общенный параметр^ который определяет, что очередь содержит ЮТ со6° елпз PERSON. Следовательно, тип данных QUEUE [PERSON] является эЛемеНТь1 @口且е直 ранее определенного обобщенного типа данных QUEUE.
Eiffel имеется несколько основных типов данных: INTEGER, REAL,
В 内 CHARACTER и DOUBLE, которые можно использовать при объявле- 8°°丄 нтификаторов. Например, ниже i и г— это ссылки на объекты типа 器Е盡 и REAL соответственно:
i: INTEGER
r: REAL
В этих примерах для наименования объектов использовались идентифика- торн из Одной буквы, но обычно бывает удобнее пользоваться более содержательными именами, чтобы программу было легче читать. В Eiffel идентификатор может состоять из одной или большего числа букв и/или цифр, если первым символом является буква. В идентификатор можно также включать символ подчеркивания (_). На число символов в идентификаторе нет ограничений, прописные и строчные буквы не различаются. Вот некоторые примеры законных идентификаторов в языке Eiffel:
address, make, phone_ntnnber, size, СЗРо, the_Date, customer1, customer2, put_char, addToQueue, PERSON
Eiffel отличает язык, применяемый для определения типов данных, от самих типов данных. Производители программ обычно предлагают клиентам обширную библиотеку часто используемых классов, таких, как списки, деревья и пРоч., дающих пользователям набор повторно используемых компонент, из которых они могут начать строить новые приложения. Несмотря на отделение языков от библиотек классов, Eiffei признает существование основных типов Данных: INTEGER, REAL, BOOLEAN, CHARACTER и DOUBLE. Каждый из них обла- /Ца^т набором стандартных операций и используется в точности так же, как те ТИпЬ1» нт。мы вводили до сих пор. Однако есть и некоторые отличия, которые МЫ теперь изучим.
В Eiffel механизм объявления объектов, в котором их создание и инициализация разделены, излишне громоздок и неэффективен для основных типов Данных. Поэтому представляется упрощенный способ. Например, объявление
i: INTEGER
и °冲丁 область памяти, в которой хранится целое число i. Кроме того, i ссь^'^тзтРУетс* значением по умолчанию нуль. Таким образом, i — это не Где на некое целое число: i непосредственно связано с местом в памяти, Ной看a”11''51 это нпсло. Это эквивалентно обычной (не указательной) перемен- Ei代е] "РУрих языках. Подобный тип данных называется расширенным типом. тнпов"°зв°"*ет связывать идентификатор непосредственно с памятью доя всех «Кть1 "аннмх, обходясь тем самым без механизма ссылок. Однако обычно объ- Р^ссматриваются как динамические в том смысле, что каждый из них
Класс
Значение no умолчанию
INTEGER REAL CHARACTER DOUBLE BOOLEAN
0
0.0
,%U' (нулевой символ) 0,0 (двойная точность) ложь
существует ограниченное время, поскольку они создаются, изменяю^существует ограниченное время, поскольку они создаются, изменяю^чтожаются в процессе исполнения программы. Механизм ссылокчтожаются в процессе исполнения программы. Механизм ссылокЧ-Ч-ет этот подход. Поэтому в Eiffel есть и ссылочные типы (обычная 廿°口^^ и расширенные типы (используемые преимущественно для основн^ 丫鼻 В следующей таблице приведены исходные значения по умолчанию &〃 '"‘ет этот подход. Поэтому в Eiffel есть и ссылочные типы (обычная 廿°口^^ и расширенные типы (используемые преимущественно для основн^ 丫鼻 В следующей таблице приведены исходные значения по умолчанию &〃 '"‘ных типов:ных типов:
существует ограниченное время, поскольку они создаются, изменяю^
существует ограниченное время, поскольку они создаются, изменяю^
чтожаются в процессе исполнения программы. Механизм ссылок
чтожаются в процессе исполнения программы. Механизм ссылок
Ч-
Ч-
ет этот подход. Поэтому в Eiffel есть и ссылочные типы (обычная 廿°口^^ и расширенные типы (используемые преимущественно для основн^ 丫鼻 В следующей таблице приведены исходные значения по умолчанию &〃 '"‘
ет этот подход. Поэтому в Eiffel есть и ссылочные типы (обычная 廿°口^^ и расширенные типы (используемые преимущественно для основн^ 丫鼻 В следующей таблице приведены исходные значения по умолчанию &〃 '"‘
ных типов:
ных типов:
Рассмотрим теперь конструкцию класса:
class <г1мя-класса>
creation
--имена процедур, используемых при создании новых экземпляров
feature
--объявления свойств класса
end — CLASS-NAME
Объявление класса может содержать разные разделы, каждый из которых обозначен ключевыми словами, вроде creation и feature. Хотя это и необязательно, рекомендуется включать имя класса в качестве комментария после ключевого слова end.
В случае ссылочных типов процесс инициал из ации означает, что мы можем построить несколько процедур создания для одного типа данных, чтобы обеспечить инициализации, пригодные для разных ситуаций. Заметим, что существование процедуры создания означает, что инициализация по умолчанию не создает настоящего объекта. Поэтому Eiffel настаивает, чтобы мы вызвали одну из процедур инициализации, а в противном случае он порождает сообщение об ошибке. Для расширенных типов, однако, поскольку процедура создания автоматически вызывается системой Eiffel при создании объекта, допускается лишь одна процедура создания во избежание неоднозначности.
Smalltalk можно охарактеризовать как сочетание языка программирован" операционной системы, среды программирования и методологии разработки программирования [17], [35]. Эту систему можно рассматривать как cpW": представления знаний, а значит, как серьезного конкурента языкам Ли^ Пролог. Она предпочтительна для разработки прототипов приложений, ° тем интерактивной графики и имитационного моделирования. Хотя пока ° ются нерешенными некоторые проблемы со скоростью исполнения (возм°* порождаемые интерпретирующей природой системы), были достигнуты в" тляющие результаты в снижении стоимости разработки некоторых разно 匕出 ностей программных систем, особенно в приложениях, содержащих сл°""
нтерактивные компоненты. Главная причина этих успехов гргкие м"” “ Amalltalk позволяет в максимальной степени восполь- ляфиче ■ «то среда 热丄皿
Р <мг 吕 ном’ ествами повторного использования программных компонент. °с^тьСЯ цреимуЩ йСП0ЛЬЗуемые в Smalltalk-80, являются объектами. Объект ов^се су^°сТИ, уисло, букву, чертеж, список, программу или редактор и в*^6北嘗:^;器ет двумя основными свойствами:
хранить данные и осуществлять доступ к ним;
・ он "^отвечать на сообщения.
• он МО廠丄
Например, объект 0.0 отвечает ет盤запишется в в呐е
0.0 sin
на сообщение sin, возвращая значение О,на сообщение sin, возвращая значение О,можно манипулировать лишь посредством передачи сообщений. Со- 学‘黑 таким образом, образуют интерфейс между объектами и внешним '"“ Способ, которым объект реагирует на сообщение, является его внутрен- м;:°^пом, и нет необходимости, чтобы он был известен вне самого объекта. Написание прикладных программ в системе Smalltalk состоит в определении объектов, которые описывают проблему, и в определении операций для каждо- г0 объекта. После того, как основные объекты данного приложения и операции на них определены, программист может использовать их, не зная подробностей их устройства; он может также повторно использовать их в других ситуациях, чем те, в которых они были построены.
на сообщение sin, возвращая значение О,
на сообщение sin, возвращая значение О,
В среде Smalltalk определено несколько типов переменных, отличающихся кругом объектов, имеющих к ним доступ:
L Переменные экземпляра. Это переменные, принадлежащие объекту, и существует две разновидности переменных экземпляра — именованные переменные экземпляра и индексированные переменные экземпляра. Имено- вань1е переменные экземпляра — это те переменные экземпляра, которые отличаются по имени. Например, в классе Point имеется две именованные переменные экземпляра, ж и у. Когда в выражении появляется имя пеРеменной экземпляра, оно отсылает к ее значению для соответствующего экземпляра. Когда создается новый экземпляр, он содержит переменные экземпляра, задаваемые его классом; значение по умолчанию для этих пев Ь1Х экземпляра есть объект, называемый nil. Доступ к индексиро- с переменным экземпляра осуществляется не с помощью имени, а й ^10 числового индекса. В отличие от объектов, обладающих лишь HbIe "HbiMH переменными экземпляра, те, что содержат индексирован- 2. 〃叩:咏"。1111b6 могут иметь непостоянное число переменных экземпляра.
^ас皆常"'класса. Это переменные, общие для всех экземпляров данного СтРоцтьс '"РммеР,Б случае компьютерного черчения окружность может Резкое И3 нескольких прямолинейных отрезков, причем число этих от- Как По оДинаково для всех окружностей; тогда это число можно определить временную класса.
Глобальные переменные. Это переменные, общие для всех обър ^Х хранятся в словаре. Например, все классы вызываются глоба^ь '°氏 О менными, имена которых являются ключом доступа к словар^^1411 W:*
Ко?Ко?Общие переменные. Это переменные, доступные экземплярам не классов. Чтобы определить набор переменных, общих для Hecxojj^j^^^^ сов, необходимо определить словарь, общий для этих классов в "*悔肌
Ко?
Ко?
сам этот словарь является глобальной переменной.
Рассмотрим теперь классы и подклассы в среде Smalltalk. Каждый в Smalltalk принадлежит к одному и только одному классу. Однако мо黑”伙】 заться полезным сделать некоторые элементы описания общими 〃仃夕□ °心' ких классов или описать один класс посредством другого. Эта возмог & реализуется в Smalltalk, где каждый новый класс описывается через п "' класс, называемый его надклассом. Экземпляры нового класса совпадаю^11
огшсан 励огшсан 励экземплярами старого, за исключением дополнительных элементов
огшсан 励
огшсан 励
класс называетсякласс называетсяявным образом определенных для нового класса. Этот новый подклассом своего надкласса.
класс называется
класс называется
Подкласс в свою очередь может иметь один или большее число подклассов Множество классов, таким образом, принимает форму дерева, корнем которого является класс, называемый Object. Object является единственным классом у которого нет надкласса. Экземпляры кажаог。подкласса наследуют все пе
ременные экземпляра, переменные класса и методы от своего надкласса.
Чтобы дать полное определение некоего класса, нам нужно задать следую* щее:
Имя: Имя класса должно быть обязательно, и оно должно отличаться от имени любого другого существующего класса.
Надкласс: Надкласс также обязателен, и он должен соответствовать одному из существующих классов.
Новые переменные класса: Переменные необязательны, но если они есть, то их имена должны отличаться от любого уже существующего имени, определенного в множестве надклассов данного класса. Можно также добавить новые методы и переопределить методы, уже определенные в множестве его надклассов.
Его новые общие переменные.
Его новые переменные экземпляра.
Список его новых методов.
Каждая сущность в Smalltalk является объектом. Следовательно, класс'。 же является объектом. Мы уже видели также, что каждьтй объект принта': жит к одному и только к одному классу. Сам класс поэтому является зкз°;; пляром другого класса, который мы называем метаклассом. В более раН версиях Smalltalk все классы были экземплярами одного-единственного класса. Однако в современной версии Smalltalk каждый класс является е口 ственным экземпляром некого метакласса по соображениям гибкости (осоо
при создании новых экземпляров класса).
ся классами и поэтому содержат методы, позволяющие еклассн 绊" кассам) отвечать на полученные ими сообщения. В ре
- /те. KJ2 ,
ем口口幺pa” I называются методами классов, тогда как другие методы 队':;т?е эти ме‘°%мн экземпляров. При создании класса автоматически со- 3^ь вак)тся м©т° 〃鼻匸以 В отличие от других классов, метаклассы не имеют ” тся н°вь1 宜 мэкземпляров метаклассов. Все они являются экземпля-
„мен; У НК ? же класса, называемого Metaclass.
амн одного и к метаклассу класса можно, послав сообщение class. На- 'Пояунм’в" класс точек, называемый Point. Мы можем связаться с 吁喘黑爲Посредством выражения
Point class
являются классами, поэтому у них есть надкласс, называемый 、1етак嘗снможем рассматривать метаклассы двумя способами:
Class. Итак,
Онм являются экземплярами класса. Metaclass.
2 Они являются подклассами класса Class.
Metaclass и Class являются двумя подклассами класса ClassDescription.
Имл переменной — это простой идентификатор, состоящий из начальной буквн, за которой следует последовательность букв или цифр. Все символы являются значащими. Обычно в Smalltalk принято нанизывать слова в один идентификатор, без пробелов, в котором каждое из слов начинается с прописной буквы, кроме, возможно, первого слова. Простые переменные начинаются со строчной буквы (например, hydeParkCorner или redPen); имена классов, глобальных переменных и переменных классов принято начинать с прописной буквы (например, LargePositivelnteger, DisplayScreen и BigBen). Это пра- внло не является обязательным, но его рекомендуется придерживаться, чтобы написанная программа была понятна другим программистам.
Smalltalk поддерживает пять видов литералов. Это знаки, цифры, строки, символы и массивы литералов. Литералы Smalltalk всегда обозначают один и тот же объект, и их можно считать литералами-константами.
* Зни咒овъсе литералы являются экземплярами класса Character. Это знаки к°“а ASCII, перед которыми стоит знак доллара, например $4 $t ${ $$ $|
J^'°6”e литералы представляют числа. Это последовательности цифр, Ную К0Т0РЬ1ми может стоять знак минуса. Они могут содержать десятич- Чйг Т0Чку с хотя бы одной цифрой до и после этой точки. Вот примеры зовых литералов:
123 宀、
/3.i4-3.140.00
Допустимы
числовые литералы в научной нотации, например
Нн ЗеЗ -3.13е-3
"HKHoij^^'TeP3*71* могут быть также выражены в системе счисления, от-
Десятичной, если перед записью числа стоит 4< base > г5. Для
оснований, больших 10, дополнительные цифры представляют
ными буквами, начиная с 'А'.1 口口^近
последовавпоследовавСтроковые литералы ~ это объекты, отвечающие
последовав
последовав
ПоПоИ они представляютсяИ они представляютсязнаков. Это экземпляры класса String,
По
По
И они представляются
И они представляются
тельностями знаков, ограниченных апострофами, например
'There' 'isn"t‘ 'any' Jmore!
Заметим, что кавычки (”) используются здесь для представления вого литерала $? в строке 'isn"tL Отдельные знаки в строке @акт"训 представляются соответствующими знаковыми литералами. ”''ск
Символьные литералы — это строки знаков, применяемые для наьм ния объектов в системе Smalltalk. Все они являются экземплярами 。訳 са Symbol. Они представляют идентификаторы и селекторы сообщу黑* обозначаются уникальными последовательностями знаков, перед кст ми стоит символ #. 1акие строки знаков должны начинаться с буквы могут содержать цифры. Некоторые примеры символов приводятся ни*;
#а #alpha2 #intersectsWith #max:
Массивы литералов — это индексированные числами структуры данных все элементы которых являются литералами. Элементы массива литералов должны быть разделены пробелами и заключены в скобки с одним символом # перед ними. Ниже приведено несколько разновидаостей массивов литералов:
(123)—массив числовых литералов,
( $а $Ъ )— массив знаковых литералов,
( 5to* 'bg' 'or' Jnot5 ,to, ’be,)— массив строковых литералов,
( jack frost )— массив символьных литералов,
( jddf 8г45 эcrazy; $b (1 2) )— массив литералов.
Заметим, что в двух последних примерах символьные литералы #jack> #frost, #jddf и массив #(1 2) не требуют помещения перед ними знака #, если они находятся внутри другого массива литералов.
Объекты могут приписываться переменным. Это осуществляется использованием префикса приписывания. Префикс приписывания состоит из имени переменной, за которым следует направленная влево стрелка. Его также м°*~ но повторять. Переменные Smalltalk весьма похожи на указатели языка С тем, что они всегда указывают на объект и они могут указывать на объекты лю6°'°
вида (или на зону памяти в С) в любой момент их жизни.
那а那аПеременная может быть приписана к псевдопеременной. ПсевдопереМеЯ ная — это специально определенный объект в Smalltalk, доступ к котоР。": возможен лишь через имя переменной. Поскольку псевдопеременные пресет% ляют заранее определенные объекты, за ними зарезервирован ограниченН , набор имен. Нам могут встретиться такие псевдопеременные, как nil, ^Р^
那а
那а
ставляющую неинициализированный объект неопределенного типа, true логической истины и false для логической лжи.
енньм должны быть объявлены до того, как они будут ис- яа 口叩'” кО разрешается использовать для переменной то же самое ^зора«Ы- ""сообщен1151. Синтаксис выражения, являющегося сообщением, пМ чТо и 冲幺’ лИ эТо имя к сообщению или к переменной.
"艺делит, °т?°:^1]—зто последний из семейства языков, общим прототипом 0 Оберон [43卜 L Алгол-60 (I960). К другим членам этого семейства отно- 0Top济 являе дч и Модула-2 (1979). Паскаль не свободен от некоторых 加.口30曲%)兄 ВБОдНЬ1х курсов программирования они несущественны, но недостатков•点н垃мИ 口卩迈 с03дании больших систем. Паскаль поощряет струк- с1asовятс^тогда как в разработке программных систем все большее
турнув пар о5рет^ет модульность. Это понятие имеет по крайней мере два значение ^Р 日拉置 известен как скрытие информации. Любая большая систе- аспекта. ^ мОдулей, разрабатываемых в значительной степени независимо на состоит 。「^(^口鼻 следует, что существует некий интерфейс, определя- 侬嚎;се свойства каждого модуля, доступные другим модулям, и скрыва- югци^се остальные. Второй аспект — технический, а именно разделъ-
компиляция модулей, при которой компилятор проверяет правильное ис- падьзование их интерфейсов. Когда некий модуль скомпилирован, описание ег0 интерфейса записывается в его символьный файл. Во время компиляции модуля-клиента компилятор берет символьные файлы импортированных мо дулей и получает тем самым всю необходимую информацию для проверки типов.
Принципиальным новшеством языга Модула-2 по сравнению с Паскалем была именно модульность, включающая скрытие информации, и раздельная компиляция. В отличие от независимой компиляции, известной из ассемблеров и других компиляторов языков, раздельная компиляция позволяет компилятору выполнять те же самые проверки согласованности типов между модулями, что и внутри отдельного модуля. Явное определение интерфейсов и сохранение полной надежности проверки типов оказалось огромным преимуществом.
Модули, экспортирующие один или несколько типов данных, вместе с набору пР°цедур, оперирующих с переменными этих типов, представляют понятие ° страктного типа данных. В этих случаях лишь имена типов фигурируют интерфейсе модуля, тогда как структура полей записей доступна лишь че- 63 ЭКСП0РтиРуемые процедуры, которые поэтому полагаются на корректность
Рых инвариантов, управляющих абстрактными типами.
строгог 6 т°Г°,Модула-2 устранила один из наиболее серьезных недостатков рЬ1 контроля типов: здесь динамические массивы вводятся как парамет- менньп^е羽Р Также примечательно введение процедурных типов для пере- миров ' а а1*© средств параллельной обработки и возможности програм- непоспр На язь1ках низкого уровня. Последние позволяют программисту как пеги енно обращаться к устройствам конкретного компьютера, таким, ва способ Р'【интерфейса,для управления операциями ввода/вывода. Это сно- °бластях СТВовало Расширению спектра приложений языка, особенно в таких 肥 мен%'皿氏 пРоектирование систем и управление процессами. Наконец что "Манно :^н。’ были устранены многие синтаксические дефекты Паскаля, а Р т°ры циклов с неопределенными условиями окончания if, while
и for. Это именно те конструкции, которые были взяты из Алгола-бО йены ради преемственности, чтобы избежать отторжения языка прОг ским сообществом, привыкшим пользоваться Алголом.
После нескольких лет практического применения модульного Пр0 вания посредством Модула-2 и других языков системного программ^ 长飞电Da
выяснилось, что конечная цель состоит в расширяемости. СтруктурнР°В^я дульное программирование были просто промежуточными этапами °"归仪 к этой цели. Введение абстракций, представленных модулями, и ние процедур, вызывающих процедуры, объявленные на более низких v ‘迎"、 иерархии абстракций, воплощает процедурную расширяемость. 0aH昌长。'"冷 успешного проектирования столь же важна расширяемость определения 如才 ных. В этом отношении язык Модула-2 неадекватен, поскольку типы не 如 быть расширены и при этом оставаться совместимыми.“货也
Объектно-ориентированные языки эффективно решили эту проблем стали модными в 80-х. Они позволяют определять подтипы Т1, называемь^ подклассами, для данного типа (класса) ТО, которые обладают тем свойство^ что все операции, применимые к экземплярам класса ТО, применимы та] же к экземплярам класса Т1. Мы осознали на этом этапе, что самым ва». ным новшеством стала расширяемость типов данных, которая, к сожалению осталась невыявленной термином «объектно-ориентированный». Этот термин дополнялся также целой системой новых названий уже известных понятий, введенной для того, чтобы подчеркнуть новый подход или новую методологию программирования «в большом». Поэтому типы стали классами, переменные —экземплярами, процедуры — методами, а вызовы процедур — сообщениями, и т. д.
Главное достоинство языка Оберон состоит в обеспечении расширяемости типов данных на основе общепринятых, легко воспринимаемых понятий, вроде типа данных и процедуры. Вследствие этого не нужно никакого разрыва с обычными приемами программирования и не требуется привыкать к целому новому классу понятий. Единственная новая возможность ~ это расширение типа «запись». Оберон поэтому объединяет традиционные понятия процедурного программирования с методами, необходимыми для получения расширяемости данных.
Эту единственную новую возможность вполне можно было добавить к яз" ку Модула-2. Зачем же было создавать еще один новый язык? Причина состояла в желании иметь язык, сохраняющий принцип процессоров. Оберон залр° сто оказывается на порядок компактнее, чем системы с сопоставимыми даже меньшими) функциональными возможностями.
Система, написанная на языке Оберон, — это иерархия модулей, больхп'Н ство из которых экспортирует один или несколько абстрактных типов '“; ных. Каждого пользователя поощряют расширять систему — эти рас皿' ния создаются простым добавлением новых модулей. Не существуй 卩或 граничения между «системой» и «прикладной программой». Кроме м° q^6- Ядро и Дисплей^ вся система написана на языке программирования рон [42].
г^людезе
4,6- °冲основные понятия объектно-ориентированного програм-
введены
В зМ诙 гв том 口исле:
мнрован ,авляет как атрибуты, так и методы посредством процес-
.Объе^- °н
са инкапсуля данных. Это набор данных и операций над этими дан- .Абстро>^ныи
ными.объектов, обладающих некоторыми общими свойствами.
Кааса ^сообщений. Она обеспечивает общение объектов друг с другом.
〃叩‘”上"'° Сне обеспечивает механизм управления классами и повтор- •竺爲閣ванне ими общих кодов.
号е 窗乂。/^^, Он придает методам дополнительный смысл, делая их более * гибкими и расширяемыми.
тт вставленные здесь понятия являются общими свойствами объектно- иентированного программирования. Они не ограничиваются каким-то кон- ^етным языком программирования. Наконец, в этой главе были описаны четЫре объектно-ориентированных язьи^а программирования, а именно Eiffel, Smalltalk, Java и Оберон.
У каждого объектно-ориентированного языка есть свои достоинства. Возникает вопрос: на каком языке нужно писать систему компьютерной алгебры? Как уже упоминалось, мы выбрали C++ в качестве языка разработки нашей системы компьютерной алгебры. Это было сделано по следующим причинам:
1.2.3.4.1.2.3.4.5.5.6.6.C++ работает быстрее других языков.
1.
2.
3.
4.
1.
2.
3.
4.
5.
5.
6.
6.
Нам нужна гибкость, предоставляемая указателями.
Нам нужно такое свойство, как шаблоны, и оно есть в C++.
Для приложений в физике и математике уже написано много программ на C/C++. Эти программы можно связать с системой компьютерной алгебры. ^^^::но связать нашу систему с Параллельной Виртуальной Машиной 誥I можем использовать Библиотеку Стандартных Шаблонов с Символь-
гося оц:^ЮШ^ главе Описываются основные средства язкка C++’ являюще- нейших И3 Самых попУлярных объектно-ориентированных языков. В даль- зике г нс йзУченные здесь понятия будут применены к математике и фи- П°МОЩЬЮ …програМмИрОванияС++
Глава 5
Основные средства языка C++
C++ не только исправляет большинство недостатков, обнаруженных но также вводит много совершенно новых конструкций, разработанныхВ этого язмка с целью обеспечить абстракцию данных и поддержку обьак胃1 ориентированного программирования. Вот некоторые из важных новых кон струкций:
Классы, основная языковая конструкция, состоящая из структуры данных и операций, применимых к данному классу.
Переменные-члены^ описывающие атрибуты класса.
Методу определяющие допустимые в данном классе операции.
Перегрузка операций, придающая операциям дополнительный смысл, чтобы их можно было применять для определенных пользователем типов данных.
Перегрузи функций, похожая на перегрузку операторов. Она позволяет одной функции иметь несколько определений, что уменьшает потребность в необычных именах функций и облегчает чтение программ.
Контролируемое программистом автоматическое преобразование mu仆°% позволяющее сочетать определенные пользователем типы с основными типами данных, которые предоставляет C++.
Производные классы, называемые также подклассами, наследующие внУ тренние переменные и методы от своих базовых классов (называемых так^е надклассами). Они могут отличаться от своих базовых классов 口。6驱"©": ем новых внутренних переменных методов или перекрытием существуй функций.
Виртуальные функции, позволяющие производному классу переопреДеЛ^ методы, унаследованные от базового класса. С помощью динам^4 两 связывания система в процессе работы выберет подходящую функци1° конкретного класса.
В нескольких следующих разделах мы покажем, как применять ? 必 вые конструкции при создании типов данных для конкретных прилов как составлять из этих абстрактных типов данных объектно-ориентиров программы.
I уделяй ссылки
5・1・ “сСЬ1Лка на данные или часть кода внутри программы. Это в
^ате"' —этО е адрес в памяти того элемента, на который он указывает.
:тральном см^яют ^м писать более гибкие программы, и это в особенности ^333Tел以 П°^Б екТНо-ориентированньш программам. Указатели применяются, Коситвк二^; перечисленные ниже ситуации:
когда вознм
2PoMм& оперирует с большими объемами данных fболее 6%К).
• Е"“ облема возникает лишь для персональных компьютеров, работаю- 9TаПРплавлением операционной системы DOS. Для больших, сложных °^ “*м область памяти 64К, выделяемая C++ под данные, может ока- 叩°好 слишком мала, чтобы вместить все данные. Указатели позволяют ^ти это ограничение.
Ког^а мЬ1 объявляем в C++ глобальные переменные, компилятор раз- wv в области памяти, называемой сегмент данных. Наибольший мещаьгх厂
размер сегмента данных составляет 64К, и здесь должны быть размещены все глобальные переменные. Для небольших программ это ограничение несущественно, но встречаются случаи, когда нам может понадобиться для этого больше памяти. Допустим, например, что программе нужен массив из 400 строк, в каждой из которых содержится до 100 знаков. Этот массив займет примерно 40 Кбайт, что меньше максимального размера 64K. Такой большой массив не вызовет трудностей, если для прочих переменных хватит оставшихся 24K.
Но что, если нам понадобятся два таких массива одновременно? На это потребуется около 80К, что не уместится в сегмент данных размером в 64K. Чтобы обрабатывать большие объемы данных, нужно использовать кучу. Программа может разместить на куче 80К, сохраняя указатель в качестве ссыжи на место размещения данных. Такой указатель займет в сегменте данных всего 4 байта.
Ес“” в программе используются данные, размер которых во время компи- ^Ции неизвестен.
У"'~9Е° вся память, отводимая операционной системой, которая не 叩力 Г Уется под саму программу, ее сегмент данных и ее стек. С помок ней 十 МЫ можем зарезервировать область памяти в куче, обращаться *0Top北сРе“ств°м Указателя и затем снова освобождать эту область. Не- задания ЭЛементы Д^ных в C++ (например, знаковые массивы) требуют 11HxMест:" Ремеров во время компиляции, даже если все отведенное под Мозкет п И Не Потребуется при исполнении программы. Простым примером Лем, СКа5^ У^ить программа, считывающая строку, заданную пользовате- СтРоковой ' ИМЯ пользователя. Если бы это имя хранилось в виде обычной Для хранр “еременн°й‘ нам бы пришлось зарезервировать достаточно места всег0 Из самой длинной возможной строки, даже если это имя состоит Неводит 蕾скольких букв. Если же размещение переменных на куче пров° время компиляции, а во время исполнения программы, мы
Зане%Зане%можем отвести как раз столько места, сколько потребуется дЛя фактических строковых данных.
Зане%
Зане%
Это банальный пример, но в приложениях, где используются добных элементов данных (например, множественные окна или СнС°Т11^ страниц из файлов), отведение как раз такого объема памяти,长^工^”^航现 обходим, может оказаться решающим обстоятельством для успещ:'哨屬、 полнения программы: иначе ей может просто не хватить памяти °Г° 厩、
Если в программе используются временные буферы данных.
Указатели и куча очень удобны в ситуациях, в которых нам ни* водить память на какое-то время, но мы не хотим занимать эту п на все время исполнения программы. Например, редактору файлов об '口 бывает нужен буфер данных для каждого редактируемого файла. В::"° того чтобы объявлять во время компиляции, что у нас будет определенно3 количество буферов определенного размера, отведенных для файлов можем отвести на эти цели ровно столько памяти, сколько нужно в данный момент времени, сделав память доступной для других целей.
Другое часто встречающееся использование временной памяти—э-^ сортировка. Обычно при сортировке большого массива данных мы делаем копию этого массива, сортируем копию и затем копируем отсортированные данные обратно в исходный массив. Это защищает целостность данных. Однако для этого нужно во время сортировки хранить две копии данных. Если мы разместим сортируемый массив в куче, мы сможем рассортировать его и скопировать результат обратно в исходный массив, затем отбросить сортировочный массив и освободить эту память для других целей.
Если программа использует много типов данных.
Один из более редких случаев применения указателей — это указание на меняющиеся в ходе работы программы структуры данных. Например, какой-то блок памяти может быть отведен под «журнал регистрации» строк символов различной длины, последовательно набираемых пользователем в поле ввода. Для чтения этого журнала некая подпрограмма будет сканер: вать этот блок в поисках отдельных строк. Мы можем использовать просТ указатель, чтобы отметить, где начинается этот блок. Это просто У1^^ нам, где находятся некоторые данные, без необходимости заранее опреД6* лять, что же именно там лежит.
Если программа использует связанные списки.
Еще одно типичное применение указателей — это объединение 又&口""^н связанные списки. Во многих простых приложениях вроде баз 理朋矇欧宏 можем хранить записи в виде массивов или в виде набираемых пол ^^^ телем файлов. Однако иногда нам бывает нужно что-то более гибко6' 耳便 массив фиксированного размера. Размещая динамические записи 助,段 каждая запись содержит указатель на следующую запись, мы см°^ 外 строить список, содержащий столько элементов, сколько нам потрещу
3To адрбс в памяти компьютера. Это может быть адрес пере- у^ателв—以口班 функции. Обычно нам неважно, в каком месте памяти на- 抵 3卽℃” Все, что нам требуется, — это сослаться на нее по имени, “с ится пере"。 ее ИСкать. Именно это и происходит в случае объявления KdP з靄/мер, если программа содержит код переменной-
int number;”
отВедет область в памяти, ссылкой на которую будет служить то комп11"*^。尔ем найш адрес переменной в памяти с помощью операто- лпшЬег- М"^рес в памяти может быть присвоен указательной переменной^ ра 9т°% адоес данных или кода, хранящихся в памяти.
int number =int *Р; р н 也number;int number =int *Р; р н 也number;Ю;
int number =
int *Р; р н 也number;
int number =
int *Р; р н 也number;
// р это указатель переменной
// адрес в памяти для number присвоен р
Пока что мы видели, как присваивать значения указателям, но от этого с tcttkv если мы не сможем получать эти значения обратно. Мы можем мало шллд j
обращаться с напечатанным указателем как если бы он был переменной соответствующего типа, разыменовав его. Чтобы разыменовать указатель, мы используем унарный префиксный оператор *. Пусть р содержит адрес переменной number; тогда *р даст нам значение переменной number. Рассмотрим следующий код:
// pointer.срр
void main()double *pl, *p2; // объявляет два указателя как double double a = 2.5, b = 5.1;void main()double *pl, *p2; // объявляет два указателя как double double a = 2.5, b = 5.1;cout Результат *Р1 = 2.5 *Р1 = 5.1cout Результат *Р1 = 2.5 *Р1 = 5.1Р1 = &а;Р2 = pl; COUt << "*р] *Р2 = Ь;Р1 = &а;Р2 = pl; COUt << "*р] *Р2 = Ь;« "*pl« "*pland *p2 and *p2and *p2 and *p2// pl// plсодержитсодержитадрес переменной аадрес переменной а<<<<*pl*pl*pl*pl<< "and<< "and<< "and<< "and*р2 = " << *р2 << endl;*р2 = " << *р2 << endl;*p2 = '* << *p2 << endl;*p2 = '* << *p2 << endl;2.55.12.55.1#include <iostream.h>
void main()
double *pl, *p2; // объявляет два указателя как double double a = 2.5, b = 5.1;
void main()
double *pl, *p2; // объявляет два указателя как double double a = 2.5, b = 5.1;
cout Результат *Р1 = 2.5 *Р1 = 5.1
cout Результат *Р1 = 2.5 *Р1 = 5.1
Р1 = &а;
Р2 = pl; COUt << "*р] *Р2 = Ь;
Р1 = &а;
Р2 = pl; COUt << "*р] *Р2 = Ь;
« "*pl
« "*pl
and *p2 and *p2
and *p2 and *p2
// pl
// pl
содержит
содержит
адрес переменной а
адрес переменной а
<<
<<
*pl
*pl
*pl
*pl
<< "and
<< "and
<< "and
<< "and
*р2 = " << *р2 << endl;
*р2 = " << *р2 << endl;
*p2 = '* << *p2 << endl;
*p2 = '* << *p2 << endl;
2.5
5.1
2.5
5.1
pl, и p2 указывают на а. Поэтому *pl и *p2pl, и p2 указывают на а. Поэтому *pl и *p2:MeiQT знатен;*;ат°Ре °°"‘ и
pl, и p2 указывают на а. Поэтому *pl и *p2
pl, и p2 указывают на а. Поэтому *pl и *p2
"енованин со「”'瓦 втором операторе cout с помощью оператора разы- J^biRaj0T^РЖимому р2 присвоено значение Ь. В результате и pl, и р2
качение 5.1.
Теперь опишем, как можно динамически использовать указатели-
Размещение динамических переменных: C++ использует ключр new и delete соответственно для создания и уничтожения динам^ ”°g переменных. Оператор new размещает динамические переменные "改敢网 Например,3 Ку%
new int;
создает один объект типа int, тогда как new int[100];
создает массив из 100 объектов типа int. Оператор new возвращает v тель к обозначенному типу. Он может быть присвоен указательной 侬' менной, вроде呻&
int *р = new int[500];
Здесь динамически размещенный массив int неинициализирован. Чтобь инициализировать объекты класса, созданного оператором new, мы используем их конструктор. Он имеет следующий вид:
Thing *р = new Thing(argument list);
Оператор new создает объект класса Thing, который инициализируется своим конструктором, сверяющим список аргументов.
Освобождение динамических переменных: переменные, созданные оператором new, должны быть уничтожены по окончании работы с ними, чтобы освободить место на куче для других динамических переменных. Для этого используется оператор delete. Например,
delete р;
возвращает память, прежде отведенную оператором new, в состояние свободного места для хранения. Чтобы освободить массив объектов, используется
delete [] р;
Скобки [] использованы здесь для освобождения произвольного массив3 объектов.
К примеру, предположим, что мы считываем некоторые строки из 6血^^ храним их в памяти. Мы не знаем точно, насколько длинными могут быть строки, поэтому нам придется объявлять достаточно длинные области пам 株。 чтобы там могла поместиться наидлиннейшая возможная строка. Если н° строки имеют наибольшую длину, то много места будет пропадать впУ ^?0, Чтобы избежать этого, мы считываем каждую строку в буфер, а затем дим ровно столько памяти, сколько нужно для ее размещения, как показ следующей программе:
//buffer'PP 如 h>
_ “de "'С1■嘗°h> // для if stream (поток входного файла) 匿lude 黨黑g.h> 〃 ДОЯ strlen (длина строки) include “
dataSize = Ю00;
] maxLengtb = 256;
const
void main。
{ stream finCLongString.dat");
:har buffer[maxLength];
char *Long[dataSize];
int i;、
for (i=0; i<dataSize; i++)
{
fin » buffer;
LongCil = new char[strlen(buffer)+l];
strcpy(Long[i], buffer);
В этой программе считываем строки из файла данных nLongString.dat". Пусть этот файл состоит из 1000 строк, каждая из которых содержит не более 256 знаков. Наша цель — сохранить весь текст в виде массива строк, названного Long. На каждом шаге мы считываем одну строку и отправляем ее в buffer. Затем мы измеряем точный размер этой строки и отводим для нее ровно столько памяти, сколько нужно. Этот процесс тщательно избегает излишних затрат
РУем этоРУем этопамяти компьютера. Вместо отведения 256К для всего массива строк (256 знаКОв на ©троку, умноженное на 1000) мы используем для текста ровно столько павдп!, сколько необходимо, плюс буфер, размер которого составляет 256 байт. стоянный ”"出工"специальный указатель, который называется this. Это по- чает Указатель на объект класса, содержащего этот метод, т. е. он обозна- —°°ъявленный самоименующий указатель объекта. Проиллюстри-
РУем это
РУем это
// // // // // // // //// // // // // // // //this.this.CPPCPP<D<Dпонятие следующей простой программой:
// // // // // // // //
// // // // // // // //
this.
this.
CPP
CPP
<D
<D
етод increment использует неявно объявленный
Указатель ■
обcnis для возврата новых увеличенных значений
Мет。: "『еменных cl emd с2.
〔ОД where т
-丄 показывает адрес
肝。Г。объекта.
^ЛЮЧвВов
Самсе СЛов° this обеспечивает встроенный оМнвавд указатель.
iQRtream.h〉
// объявление класса Cpair class Cpair
private:
char cl, c2;
public:
Cpair(char);
Cpair increment();
void print();
void where^am.K);
// Определение класса Cpair
Cpair::Cpair(char c) : c2(c), cl(l+c) {}
Cpairft Cpair::increment() { cl++; c2++; return *this; } void Cpair::print() { cout << cl << " and " << c2 << endl; } void Cpair::where.am^lO { cout << this << endl; }
void main()
Cpair x( ^AO ;
х.print();
х.where„am.IО;
// вывод: В и A
// вывод: 0x7fffaee0
Cpair z(X); z.where^am.lO ;
// вывод: 0x7fffaedc
z. increment (); z.print();
// вывод: Z и Y
z.where-am_I();
// вывод: 0x7fftaedc
Cpair n('l');
n.increment().print();
// вывод: 3 и 2
n.where^am.K);
// вывод: 0x7fffaed8
В C++ указатели являются мощным средством. Однако при их при^ейеЯ
нужно избегать двух часто встречающихся проблем:
Разъьменовъьвание неинициализированных указателей. Один из 口吗‘长&长1‘ точников ошибок — применение неинициализированных указателей- & для всех прочих переменных в C++, значение указателя остается не° ^冰^ ленным, пока мы не присвоим ему некого значения. В принципе °н указывать на любое место в памяти. Если разыменовать такой ука^'
“ тур биты. Это может стать фатальным, если мы присвоим ^уЧатся а】」с щности, на которую он указывает, потому что может ока- “ кие зна"'"”' некий важный сегмент данных, например программ- "ать^ nep。1111c ^ операционная система. Это звучит немного страшновато, ;8重长0反好11A o访 дисциплине с этим нетрудно справиться.
но прй неко^^ать потенциально опасного разыменования неинициализи-
ниже:ниже:e1 -^ 工2 一^ 工3 一^ NULLe1 -^ 工2 一^ 工3 一^ NULLЧт°бы 113 宜 C++ содержит ключевое слово NULL, которое можно …не указывающих ни на что. Указатель NULL йсцользовать ^ему не привязан. Одно из частых применений указателя законен; ^0 связанный список. Пусть L1.L2.L3 — элементы связанного NULL —°^^^ указать, что связанный список исчерпан, обычно делается списка. „„рттрпний элемент указывал на указатель NULL, как показано ^, 4тобы н0з …
ниже:
ниже:
e1 -^ 工2 一^ 工3 一^ NULL
e1 -^ 工2 一^ 工3 一^ NULL
Утечка кучи. Другая частая проблема, связанная с использованием дина- • мйЧесКих переменных, известна как утечка кучи. Это ситуация, при котой память размещается в куче и затем утрачивается — по какой-то причине указатель более не указывает на размещенную область памяти, так что на нее нельзя сослаться или освободить ее.
Частая причина утечек кучи ——это повторное присваивание значений динамическим переменным без уничтожения предшествующих значений. Например,
#include <iostream.h>
void main()
{
int* ptr;
Ptr = new int[500];
} Ptr = NULL;
Указательной переменной ptr сначала было присвоено значение указателя ^массив целых чисел из 500 элементов. Сразу после этого ей было присво- □"с ЗНаЧение MULL. Это сделало область памяти (массив int), на который иначале丿
можно g 1вала переменная ptr, потерянной, и восстановить ее невоз- ^ время “°"""11。边 ситуации память утекает. Значительная утечка памяти из-за нехваСП°ЛНеНИЯ м°*ет привести к аварийной остановке программы
Д^ляет 口^£°“"工"конструкция, называемая ссылкой. В сущности, она опре- ^CHqecKg это ' ИЛи Другое название для любого экземпляра данных. Син- ветствую осуществляется приписыванием слева знака & к имени соот-
mt ii = 5.1工;mt ii = 5.1工;ТИпа Данных. Например,
mt ii = 5.
1工;
mt ii = 5.
1工;
// Автоматическая переменная ii
〃 Указатель на int, который указывает на ii
// Ссылка на переменную ii
Теперь мы можем использовать ri повсюду, точно так же, как Предположим, что мы написали
ri *s 10;// Умножение ri на 10
Значения переменных ii, *pi и ri становятся равными 50. Напримеп рим следующую программу:' ‘武的叱
// ref.cpp
#include <iostream.h>
void main()
int i = 23;
int *ip;
ip = &i;
cout << "i = " << i << endl; // i = 23
cout << "ip = “ << ip << endl; 〃 ip = 0x7fffaed4 cout << "&i = " << &i << endl; // &i = 0x7fffaed4
int &j = i; // псевдоним j co значением 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 пример использования ссылки в качестве зргуме"
52 Классы
Класс — это важнейшая конструкция языка C++, применяемая для °)"长的 абстрактных типов данных (АТД). Она описывает способы поведения °&
такие, как атрибуты, операции, типы аргументов для операций и『邸 “ Общий синтаксис объявления класса следующий:
dawue> методы>dawue> методы>crivate:
dawue> методы>
dawue> методы>
Р <за酬仇771仇е
《3a駡7仅0仇6
PГ°黑爲ё…е М*乙
Защищенные МетодЫ>
^^открытые данные>
^открытые методы>
в с++ предлагают три уровня видимости для объектов данных и *驚二\ublic, protected и private. Эти уровни называются специфика- м*震 доступа. У каждого вида доступа есть свои достоинства, как описано ниже:
private: Только методы класса имеют доступ к закрытым данным. Экземплярам класса доступ к ним закрыт.
protected: Только методы класса и его классов-потомков имеют доступ к защищенным данным. Экземплярам класса доступ к ним закрыт.
public: Данные видимы методам класса, экземплярам класса, методам классов-потомков и их экземплярам.
Далее мы перечислим некоторые простые руководящие правила надлежащего использования классов:
Спецификатор доступа может появиться в любом порядке.
Один и тот же спецификатор доступа может появиться более одного раза.
Если спецификатор доступа опущен, то компилятор считает данные защищенными (protected).
Избегайте помещения внутренних данных в открытую (public) область, если только такое объявление не упрощает существенно разработку про- граммьг.
т^треннле данные обычно помещаются в защищенную область, чтобы ме- • Sen: КЛаССОв потомков мотай иметь к ним доступ.
Нь1х "Ь:3У汪те методы для изменения или запроса значений внутренних дан-
Посд^ тор。
°^ъявления 0* кпа^с был объявлен, имя класса можно использовать для ^ьнв^енне H0Bbix экземпляров класса. Синтаксис объявления напоминает "остроум пеРеменнь1х, В оставшейся части обсуждения мы шаг за шагом н”м использу те"”*汪 вариант класса String. По ходу построения мы объяс- вариант класс /^ понятия и языковые конструкции языка C++. Улучшенный
В обвп1HoM■ ‘‘‘ng будет представлен в гл. 6.
Ут。стрп ЯЗЬ1ке С строка представляется указателем на char. Это озна- в“"Ннн коне” с"。*”。рассматривать как массив типа char. В таком пред- Строки обозначается сочетанием знаков '\0'. Это условное обо-
значение страдает одним крупным недостатком. Для '\0' нужно 0ТВе байт, и это сочетание является частью строки. Значит, для хранен ‘丁此近 «аЬс» потребуется 4 байта памяти. Несоответствие между длиной 凡必瓦; количеством отводимой под нее памяти весьма смущало многих п ‘?口。出「 лей. К тому же это расточительно. Поэтому, чтобы избежать ощик b3°b^f- высить эффективность, лучше всего реализовать строку как абстрак * 与比入 данных.т” 垃屯 Trq
В приводимом ниже листинге показано типичное описание класса реализация. Как мы видим, у класса String имеется два поля данных 玄"” вое поле, datalength, содержит размер самой длинной строки. Второе 口附 data, — это указатель на тип char. После его размещения в подходЯщей сти памяти оно будет использовано для хранения знаков строки. Чаще поля данных объявляются закрытыми с целью скрытия данных. Однако не законно помещать их в открытую область.
// estring.h
#include <iostream.h>
#include <string.h>
#include <assert.h>
class String
private:
// Поля данных
int datalength;
char *data;
public:
// Конструкторы и деструктор
StringO ;
String(char);
String(const char *);
String(const String &);
~String();
// Оператор присваивания
const String & operator = (const String &);
// Метод
int length() const;
// Оператор преобразования
operator const char *() const;
// Друзья: конкатенация и поток вывода
friend String operator + (const String &, const String 也); friend ostream & operator << (ostream 也,const String &);
};
// Реализация класса
И деструктор “снснрукн°р,\ . datalength(l) , data(new char[l])
//StringD '
5炉吐
/l= NULL),
Y %、。,;
dataL^J
./char c) : datalength(2), data(new char[2]) String::' ■
{ uert(data != NULL);
assern
dataEOl 二 c;
.:String(const char *s)
Strl datalength(strlen(s) + 1) , data (new char [datalength])
assert(data != NULL); strcpy(data.,s);
String:: String (const String &s)
:datalength (strlen (s. data) + 1) 3 data (new char [datalength])
assert(data != NULL); strcpy(data,s.data);
String: : "StringQ
} delete [] data;
соП^е?аТ°Р усваивания
{ 工工口目也 String;:operator = (const String & s) f(” this)
*ete 口 data;
詆小=strlen(s.data) + 1;
a^ert(::=职 datalength];
、strcpy!= NULL);
} PyUata,s.data);
} F nhis;
‘7548
int String: : lengthO const return strlen(data);
// оператор преобразования
String::operator const char *() const
return data;
// оператор конкатенации
String operator + (const String &sl, const String &s2)
String S(sl.lengthO + s2.lengthO);
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・ Конструкторы и деструктор
Два самых важных метода класса — это конструктор и деструктор. В к"'" может быть много конструкторов, но у каждого класса только один ДеСТ?^ тор. Назначение конструктора — создавать экземплярьг класса, т. е. °°" Его можно использовать для выполнения инициал из ации и размещени 曾* мической памяти для объекта. Заметим, что конструкторы всегда имеют ^^ имя, что и класс. Деструктор, напротив, применяется для уборки по^^ как объект уничтожен. Процесс уборки обычно освобождает неиспо^^ ную память, возвращая ее в систему. Он имеет то же название, что и с добавлением префикса «тильда» (~).к^
Для введенного здесь класса String есть три перегруженных коне р
Каждый из них отвечает за свой собственный тип построения:
StringO ;
например, String х; String у=0; String z=" ”;工我虱,淑
присвоить переменным х, у и z значения «пустая строка»- ЗаМе
и " " можно использовать в качестве строки типа NULL.
戸询邂^嗤ringx(,A,),У(,#>.
аПрммеР’ J 1енной х значение А, а переменной у значение # соответ- присвоив' пере卜
fTBeHH%onst Char*)
3 string" string x("abc");
tIanP«Mep, менной x строковое значение «аЬс».
присвоить Р
в каждом конструкторе данные размещаются в определенной
Замшим, gio всеГда необходимо, так как области памяти должны быть области 口‘м只::стемн для хранения строк. Однако динамически размещенная выделены Й^вО^оЖдается системой автоматически, это должны делать пользо- память ^свО^ОЖдение неиспользуемой памяти важно для того, чтобы она мо- 15атели, н0 использоваться для последующих размещений. В C++ деструк- и” П^ользуется для освобождения памяти, когда переменные находятся вне Т°Р видимости. Это делается неявно самой системой. Деструктор для нашего ;;;:с: строк определяется следующим образом:
String::~String() {
delete 口 data;
Квадратные скобки [] используются здесь для удаления всего массива.
Конструктор копирования и оператор присваивания
Как можно догадаться по названию, конструктор копирования использует-
"Для дупликации содержимого экземпляра в операторе объявления. Кроме того, он будет вызван для порождения временных значений, когда аргумен- ^^класса передаются как параметры-значения. Если поля данных не содер- к Никаких Указателей, которые нужно инициализировать динамически, то Работает т°Р к°пирования, созданный компилятором и копирующий почленно, “аннне "Рави^ьно. Однако если поля данных содержат указательные пере- Воз;£ необходим подходящий конструктор копирования.
5'5'ПиРУется с"' * пРимеРУ, наш класс String. Пусть строка SI = "Computer" ко- скопироваЛ°Ш'ЬЮ почленного конструктора копий в другую строку S2. Бу” S\ и S2 vJ а'。" указателя строки S1 вместо ее содержимого "Computer", больше не НуЖ1^Вают на Who и то же место в памяти. Если строка S2 уже ;°т°рое указывае7 '° 6У“ет вызван деструктор String, и место в памяти, на ';МГказатеп^т:?' будет освобождено. Это сделает S1 повисшим указате- o;rua оставгцаяс; УказЬ1вающим ни на какой задействованный блок памяти. ^°бодИТь бпок' ''Рока S1 будет уничтожаться, оператор delete попытается ект^Ь'Мм, если1^МЯТИ,КОТОРЫЙ У尔е был освобожден. Это может оказаться 川兄ь【・ ПоэтоМу бь^т^т блок памяти уже был отведен под какие-то другие объ- 0ro опре Ь1 пРавильно всегда создавать конструктор копирования "е”яемого пользователем класса.
5'
5'
В нашем классе String конструктор копирования в качестве аргумента, создает новый буфер памяти аргумента, как показано ниже:В нашем классе String конструктор копирования в качестве аргумента, создает новый буфер памяти аргумента, как показано ниже:берет экземПл,берет экземПл,и копируети копирует:яр:яр8跳8跳
В нашем классе String конструктор копирования в качестве аргумента, создает новый буфер памяти аргумента, как показано ниже:
В нашем классе String конструктор копирования в качестве аргумента, создает новый буфер памяти аргумента, как показано ниже:
берет экземПл,
берет экземПл,
и копирует
и копирует
:яр
:яр
8跳
8跳
String::String(const String &s)
:datalength(strlen(s.data)+1), data(new char[datalen tK
{」)
assert(data != NULL);
strcpy(data,s.data);
H3 6砲*H3 6砲*где strlenO, strcpy() суть процедуры из <string.h>, a assert() теки assert(h).
H3 6砲*
H3 6砲*
char *strcpy(char *sl,const char *s2);
\°'.вс.\°'.вс.Копирует строку s2 в строку si, включая терминальный символ вращает значение si.
\°'.вс.
\°'.вс.
size_t strlen(const char *s);
Возвращает длину строки s, где длина определяется как число знаков е строке без терминального символа 'О'.
void assert(int expr);
Если expr равно нулю (ложь), то печатаются диагностические данные и программа останавливается. Диагностические данные включают выражение, имя файла и номер строки файла.
Оператор присваивания необходим всякий раз, когда поля данных требуют динамического размещения памяти или когда конструктор копий класса нужно переписать. Для нашего класса String его определение следующее:
const String feString::operator = (const Stringfe s)
if(&s != this)
{
delete [] data;
datalength = strlen(s.data) + 1;
data = new char[datalength];
assert(data != NULL);
strcpy(data,s.data);
return *this;
}
Из приведенной выше распечатки видно, что эта функция похожа на к°
тор копирования, но отличается от него двумя важными чертами: 『 • Оператор присваивания должен обрабатывать особый случай, ко『4:值壯" присваивается самому себе. Отсюда понятно, почему эта функция i ^.^ ся с оператора if. Условный оператор гарантирует, что наш оператс^^ % ‘ работать правильно, когда строка присваивается самой себе, напр^^ 代时 х; дело тут не только в том, что излишне присваивать что-либо са“ * “
^затели data и s. data указывают на один и тот же адрес.
z«peДРУг°е,ппиэтом delete [] data; и strcpy(data,s.data) ; pa-
^^лй прав11'。" оператор возвращает ссылку String&. Это позволяет one-
■炉㈠
String х> ^„I'e Shuttle";
ьно Возврат ссылочного типа сделал возможным левосто- ^ХХвание.
5 5. Преобразования типа
ся много правил преобразования типа, которым подчиняется ком- В ппи изменении значения объекта от одного фундаментального типа к П1 . 9TH правила позволяют производить действия с операндами, не совпадающими по типу. Такие операции удобны, но потенциально опасны, если использовать их ненадлежащим образом. Рассмотрим следующий пример:
void voidvoid voidf(long); f(int);f(long); f(int);void voidvoid voidg(long);g(unsigned int)g(long);g(unsigned int)voidvoidmain ()main ()〃 convert. схх
void void
void void
f(long); f(int);
f(long); f(int);
void void
void void
g(long);
g(unsigned int)
g(long);
g(unsigned int)
void
void
main ()
main ()
f('A〉); g(P); }f('A〉); g(P); }
f('A〉); g(P); }
f('A〉); g(P); }
// Компилятор выбирает f(int) // Неопределенность!!!
Заметим, что ч 口
зовалась^ 6 """ преобразования знаков 'А' и〉Р‘ в целые числа исполь- ^ии из-за н ца АЬСИ. Однако эта программа порождает ошибку компиля- неявное 叩齡^1'%‘"61111。*3!^ между g(unsigned int) и g(long). Кроме того, "сполнення п разование может привести к малозаметным ошибкам во время 必к Работают ГраМмы, KOT°pbie трудно обнаружить. Поэтому полезно знать, 06P心ованця 11rlp'в”"а преобразования в C++. Общие правила неявного пре- ИПа включают два этапа.
L снаЧала Ти
кидается 口"比",short, enum преобразуются в int всякий раз, когда ставить в виде -^НИ Прео^РазУются в unsigned int, если их нельзя пред-
口 анн0口 анн0согласносогласно>【Х F>【Х FЕсли выражение по-прежнему содержит смешанные типы
口 анн0
口 анн0
согласно
согласно
>【Х F
>【Х F
низкие по рангу типы преобразуются в более высокие иерархии:
int Т unsigned int Т long т unsigned 10п§—> float Т double —> long double
Полученное значение смешанного выражения имеет тип того опер рый занимает более высокое положение в иерархии типов. Пусть ©”%%1^叱 2 и long b = 3; тогда операция a+b делает следующее:"必 а;
b преобразуется к типу double.
Выполняется сложение по правилам double, и полученный ре3ул имеет тип double.
Явное преобразование использует каст. В обычном С каст имеет слеп вид:吨
(тип) выражение
тогда как в C++ возможна и другая форма (функциональная нотация) тип (выражение)
Например, следующие два эквивалентных выражения преобразуют int и £ 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. Такой способ преобразования работает только в случае преобр33 от уже определенного типа к определяемому пользователем типу-" 上
№
№
но добавить какой-либо конструктор ни к какому встроенному т员 11y 。口旷 (скажем, к int или double). Иногда бывает желательно преобразо0^^p^Ni^1, деленный пользователем тип ко встроенному типу данных. Пусть, "р。" мы хотим, чтобы класс String работал с теми функциями обработ которые имеются в традиционном С:
.ь qf"Microscopen);String s( 1.ь qf"Microscopen);String s( 1
.ь qf"Microscopen);
String s( 1
.ь qf"Microscopen);
String s( 1
int 1if(i cout}Результатint 1if(i cout}Результатstrlen(s), j = s.length();
int 1
if(i cout
}
Результат
int 1
if(i cout
}
Результат
<< “me string " << s << " consists of " << i « " characters " << endl;
Строка Microscope состоит из 10 знаков
В этой программе компилятор автоматически преобразует String в char * в аргументе функции strlen(). C++ имеет для этого специальную функцию преобразования. Эта функция преобразования имеет следующий общий вид:
operator 力如е() const { . . . }
二л* нашего класса String функция преобразования — это
operator const char *() { return data; }
Первое const в этом выражении означает, что память, на которую указыва- 】аь^вРа"аем°е значение, не должна изменяться программой, внешней для объект КЛасса, а второе говорит, что этот оператор не меняет содержимого Пре j”11* String, на который она действует.
丁曰口兄 можеа3°ВаНИЯ ТИПОВ пРименимЬ1 также к указателям. Любой тип указа- общего ЖеТ ^ЫТЬ преобразован к указателю общего вида void*. Но указатель Указателк,Л'а»НеО^ХОДИМО пРе°бразовать к явному типу, если он присваивается "°' переменной необщего вида.
〃 generic.схх
#1I1C1Ude <i°stream.h> /d main ()
int * »
generic^ptr = п;
n = (int*)generic_ptr;
n = generic_ptr;
// ок〃 ок// Ошибка!!!// ок〃 ок// Ошибка!!!Эта программа порождает сообщение об ошибке компиляции:
// ок
〃 ок
// Ошибка!!!
// ок
〃 ок
// Ошибка!!!
значение типа "void *" нельзя присвоить объекту типа "int “ n = generic.ptr;
Нулевой указатель может быть преобразован в указатель любого ТИпа.
char *ptrl = 0;int *ptr2 = ptrl; int *y = 0;char *ptrl = 0;int *ptr2 = ptrl; int *y = 0;// Неправильно: нужно (int *)ptrl•
char *ptrl = 0;
int *ptr2 = ptrl; int *y = 0;
char *ptrl = 0;
int *ptr2 = ptrl; int *y = 0;
// OK'
Указатель на любой класс можно преобразовать в указатель на класс u> г п, кота
рого он был произведен открытым образом. Зто относится также к ссылкам
Рассмотрим простой пример, относящийся к абстрактному типу данных ] классу Verylong. Этот класс включает лишь большие положительные целые числа, превосходящие предельное значение встроенного типа unsigned iorLg Это упрощенный вариант класса, который будет рассмотрен в гл. 6.
В примере мы сфокусируем наше внимание на преобразовании типов в классе. Для абстрактного типа данных существуют преобразования двух видов. Преобразование первого вида преобразует данные встроенных типов в абстрактный тип данных. Они совершаются конструкторами класса. Второй вид преобразует данные абстрактного типа во встроенный тип. Этот вид преобразований совершается оператором преобразования, имеющимся в C++,
// cast.схх
#include <iostream.h>
#include <string.h> ttinclude <math.h>
class Verylong
{
private:
char *data;
public:
Verylong(const char * = NULL);
Verylong(unsigned long);
Verylong(const Verylong &);
Verylong & operator = (const Verylong &);
"VerylongO ;
operator int() const;
friend ostream & operator « (ostream &, const Verylong &)
};
ifif* data 二 ^w,} else* data 二 ^w,} else
if
if
* data 二 ^w,
} else
* data 二 ^w,
} else
l^Veryl°ng3nst char "value) Veryiwg"
char[strlen(value) + 1]; value);
{— new ch.3.2? [11 ;
♦data = '。’
2。吟 VeryXunsigned
long n)
1;口;1;口;int digits;
1;
口;
1;
口;
digits = (int)loglO(n) + data = new char [digits + data[digits—] = '\0‘;
while (n >= 1) // извлекаем число цифра за цифрой
data [digits—J = n%10 +,0,; n /= 10;
}
)
Verylong: :Verylong(const Verylong &x) data = new char [strlen(x. data) + 1];
} strcpy(data, x.data);
{ rylong 也 Verylong::operator = (const Verylong &rhs)
}
if (this else {
delete data =
}
=&rhs) return *this;
口 data;
$十anew char [strlen (rhs. data) + 1];
r「Py(data, rhs.data);
etUrn *this;
Verylong::"Verylong() { delete [] data; }
Verylong::operator int() const
int a, number = 0;
a = strlen(data);
if (a > 5)
cerr « "Преобразование невозможно" << endl;
return 0;
else
{
for (int j=a; j>=l; j—)
number += (data[a-j] - 48)*pow(10,j-1);
return number;
ostream& operator « (ostreamfe os, const Verylong& x { return os « x.data; }
void main()
// string => Verylong
Verylong sl("1234");
cout << "si = " « si « endl; cout << endl;
// Verylong => int int n = si;
cout << "n = 11 << n << endl; cout « endl;
// Число Verylong выходит за диапазон int
Verylong s2("7777777777");
cout « "s2 = " << s2 << endl; cout « endl;
// Verylong => int
int m = s2;
cout << ”m = " << m « endl; cout << endl;
// unsigned long => Verylong
Verylong nl(234567890);
cout « "nl = ” << nl << endl;
:23456789。
• 6 Перегрузка операторов
нн状 оператор может иметь более чем один смысл — это зависит В C++ встроен рассмотрим бинарный оператор сложения а + Ъ. Типами “о ар「У н 匕 могут служить int, float, double или даже определенный еннМХ 冲'м мп, скажем Matrix. Оператор сложения матриц несомненно "山3c^тьно отличается от сложения чисел с плавающей запятой. Мы будем “ппрпатоо + имеет более чем один смысл. На самом деле и многие
'операторы в C++ могут быть перегружены, как показывает следующий ([ШСОК:
ТипУнарныеТипУнарныеАрифметические Сдвиги Отношения Побитовые Логические Присваивания Доступ к данным ПрочиеАрифметические Сдвиги Отношения Побитовые Логические Присваивания Доступ к данным ПрочиеОбозначение оператора
Тип
Унарные
Тип
Унарные
Арифметические Сдвиги Отношения Побитовые Логические Присваивания Доступ к данным Прочие
Арифметические Сдвиги Отношения Побитовые Логические Присваивания Доступ к данным Прочие
++ (префиксная / постфиксная)
--(префиксная/постфиксная) & (адрес объекта)
* (разыменование) + - ~ ! (тип)
* / % 一
« >>
>>=<<===!=
& " I
&& I I
=*= /= %= += -= <<= >>= &= л= I=
[]-> ->*
О , new delete
И ВСе операторы в C++ могут быть перегружены, кроме:
a.ba. *ba: :ba?b: сa.ba. *ba: :ba?b: срЫб°Р объекта р:::менование указателя Условий™6 °бласти видимости
a.b
a. *b
a: :b
a?b: с
a.b
a. *b
a: :b
a?b: с
ВНЬ1И оператор
'1七 не Мож;:*"° пеРегРужать лишь определенные в самом языке операторы, 《а'ениа ** 口^'。4tlть никаких новых обозначений для операторов, вроде обо- в степень, используемого в Фортране.
Нс:Нс:и3менить н м。*11。пРидавать дополнительный смысл, порядок выполне- Клю^аю1пее1^?ЗМожно- Предположим, мы хотим перегрузить оператор 宀 Н)> придав ему смысл возведения в степень. Нам придется
Нс:
Нс:
проявить крайнюю осторожность в отношении порядка вычислена ■ ние a+b*c"d будет вычисляться как (a+(b*c)) "d, а не как а+(ь*(с: 匕‘叫 выражение было правильным, нужны дополнительные скобки.
Фактически в C++ есть два вида перегрузки операторов:加, modoe класса и глобальная перегрузка. Снова рассмотрим операто °''*" а + Ь, где а и Ъ суть объекты класса С. Общий вид перегрузКа ''"‘咏? класса следующий:"口* 力电上
С С::operator + С { ... }
В этой программе выражение а + b означает a.operator+(b). Мм м же перегрузить этот оператор с помощью глобальной перегрузки: 学
friend С operator + (С,С) { ... }
оператора нужно объявить сиот* тассе. Дружба между функщщ;;оператора нужно объявить сиот* тассе. Дружба между функщщ;;где а и b суть первый и второй параметры данной функции соответст Заметим, что для глобальной перегрузки ствующую функцию как friend в этом к классами обсуждается в разд. 5.9.
оператора нужно объявить сиот* тассе. Дружба между функщщ;;
оператора нужно объявить сиот* тассе. Дружба между функщщ;;
Рассмотрим один пример перегрузки оператора. Пусть мы хотим перего- зить оператор сложения +, чтобы осуществить конкатенацию двух сбъек; класса String.
// concat.схх
ttinclude <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
Если бы мы использовали для этого оператора перегрузку мето"。6^^) са? то "Му " + S1 было бы проинтерпретировано как "Му "・°Рега'^自 м匕 Это ошибка, так как "Му ” не является экземпляром класса. Ни@ 口姆ор тод не может применяться к "Му ". Это происходит потому, что к° 口且必 C++ не производит автоматического преобразования левого операнда 口现仔 го метода. Поэтому такая программа не может работать без глобаль грузки.
стОР'стОР'глобальная перегрузка операции + преобразует выра- угон,,0HbgbIpan<eHHe operator+(String("My ") ,S1). Компилятор
стОР'
стОР'
С Г?〃у "十 S1 ^разует левый операнд функции + в объект типа String. 冰""[丁依乱® 叩‘° аботает конкатенация, рассмотрим следующий код:
;[то6ь【п°н+ (const String &sl, const String &s2)
String ед
{length。+ s2. length»);
霖a,気data);
:農眾S.data, s2.data);
return S;
} еоирует временный буфер S подходящего размера, копирует S1 в 9'°т "°盤照2 позади S с помощью strcat О .
S и п°м 屮 с еще одного примера рассмотрим семейство линейных операторов мГна векторном пространстве V со скалярным произведением. Коммута- °' иные соотношения для этих операторов (так называемых операторов Бозе)
даются формулами
(5.1)
где / — тождественный оператор, а
[Ь, Ь]—[乩 6叮—О,
где 0 — нулевой оператор, [,] означает коммутатор. Оператор 6 называется оператором уничтожения^ а оператор 6t — оператором создания. Векторное пространство V должно быть бесконечномерным, чтобы выполнялось (5.1). Пусть |0) есть вакуумное состояние, т. е.
“0〉=0, (0|0) = 1.(5.2)
Любое состояние задается формулой
(炉广|0〉,где n - 0,1,2,....
То^""76。п^имеРа рассмотрим оператор Н — b^bb^b и состояние |ф)二 b“0〉.
h\ф)=川儿卄炉|0)
[в силу (5.1)]=户儿十(/ + Mb)|O)
[в силу (5.2)] = b%b“O〉
[в силу (5.1)]=计(/ + 力力0)
В 5е电 1Gl^ 口 【В снпу (5.2)j =山0〉.
в оператор со 口Р0rpамме мы перегрузим оператор сложения, превратив его °ператор* де^^НИя,а оператор вычитания — в оператор уничтожения. Эти Wy「oe состодн^вурт на вакуумное состояние |0), и в результате получается Ие' 口"* иллюстрации используется несколько операторов:
”1。}, Ь|0), Ь^|0), ЪЬ^ЬЧ^О), b版Е“0).
// bose.схх
#include <iostream.h>
class State
private:
int m; // число операторов создания, действующих на
int factor; // число идентичных состояний*акуу“
public:
// Конструктор
State();// создать состояние
// методы
void display() const; // показать состояние
void bose_state(int); // операция на состоянии
void reset О; // восстановить состояние
};
// оператор Бозе class Bose
public:
// Конструктор
Bose();
// Операторы
State operator + (State &); // оператор создания
State operator - (State &); // оператор уничтожения };
State::State() : m(0), factor(1) {}
void State::bose_state(int bstate)
{ if(bstate == -1) factor *= m―; else m++; }
void State::display() const
if((factor) cout << “0";
else
cout << factor << "*";
if (m)
cout << "(";
for (int i=0; i<m; i++) cout « nb+";
cout << ")";
cout << "I0>";
}
{ m ,。; factor
=1; }
Qd Stat,」
.♦BoseC 力
产..*3 + Me &
6田ё
§1(§2);
噎 state ⑴; sl.bose-一 retS si,
s2)
State Bose::叩田。—(State &s2)
{
:北 sf 窗:C);
return si;
}
void main。
{
State g; Bose b; g = b+ g; cout « "g g.reset(); g = b- g;
g.disp工ay();
cout
cout <c "g g.reset();
=" •
g.display();
cout
g = b- (b*
g);
cout « "g g-reset();
g.displayO ;
cout
g = b- (b+
(b+
(b+ g)));
cowt « Hg g-reset();
g.displayO ;
cout
g = b+ & cout « %
(b+
(b- (b+ g))));
、g
g.displayO ;
cout
<< endl;
<< endl;
<< endl;
<< endl;
<< endl;
Pe: ==: g : g : g : g = g :
"зультан
=』==工
:l*(b+)|0>
0
l*lo>
沙b+)心(b+)lo>
|0)|0)(5岛(5,6i построить 用(5岛(5,6i построить 用
|0)
|0)
(5岛
(5,6i построить 用
(5岛
(5,6i построить 用
Теперь давайте рассмотрим оператор Ферми. Возьмем семейст операторов с(, cj для j = 1,... ,п, определенных на конечномерна ° " пространстве V и удовлетворяющих антикоммутационным「门八
lcj^k]+ =[斗疝+ = 0,也ц
[勺,由+ =3汴,’)?
где 0 — нулевой оператор, I — единичный оператор,么卜=1,2 .付4
ры, удовлетворяющие уравнениям (5.3) и (5.4), называются операт^。口%'% чтожения и создания фермионов.
Определим состояние |0) (так называемое вакуумное состояние) 。6 щее следующими свойствами:' 北嗚卜
с/0) = 0 при j = 1,2,... , п,
⑼。)=L
Состояние |0) нормировано. Другие состояния теперь можно и операторов создания &
Применяя приведенные выше правила, находим
cic444io)
[по (5.4)] = 一 Q《C4dls
[no (5.4)] = 一 qc[(/ - “cJS
[no (5.5)] = - q/l。)
[no (5.4)] = - (/ - cJci)|O)
向。(5・5)j - |0)
В следующей программе мы реализуем операторы Ферми и приведем несколько примеров:
// fermi.схх
#include <iostream.h>
#include <assert.h>
#include "MString.h11
// Информация о все еще активных операторах содержится
// в классе Power
{
public:
String с;// название оператора, напр. cl, с4+
int и;// степень оператора, напр. (с4+)"2
class State {
private:
// умножение состояния// умножение состоянияint factor;
// умножение состояния
// умножение состояния
число разных указатель на
операторов
Power
//операторприсваивания//операцияна состоянии//показатьсостояние//обновитьсостояние//операторприсваивания//операцияна состоянии//показатьсостояние//обновитьсостояние
//
оператор
присваивания
//
операция
на состоянии
//
показать
состояние
//
обновить
состояние
//
оператор
присваивания
//
операция
на состоянии
//
показать
состояние
//
обновить
состояние
pub 力'конструктор
State。;
Г = (State &);
void °Per creatOr(String, int);
囁篙暇…
void resets,
};
〃 оператор Ферми class Fermi
{
private:
String f; // хранит
название
оператора, наир.
cl, с4
public:
// Конструктор
// оператор создания// оператор уничтожения// оператор создания// оператор уничтоженияFermi(String);
// оператор создания
// оператор уничтожения
// оператор создания
// оператор уничтожения
State operator + (State &);
State operator - (State &);
State: :State() : m(0) , factor(l) , p(NULL) {}
}d State: :operator = (State & s2)
m = s2.m;.
厂 new p°wer[m]; assert(p);
:Г (int i=0; i<rn; i++) p[il = s2.p[i]; factor = s2.factor;
Void State -•
(' 'mi»creat or (String ch., int s)
;(factor)
〃 E,c2] + — r
for (①七 i二c 二 kl+,c2+]_+ = [cl,c2+]_+ = 0 if ((□+•、、' ''т && (ch ! = (p+i)->c) ; i++)
1 、" 4 2) factor *= (-1);
// аeт“
if (1==m)новы显 оператор
if (s==l) // оператор создания
{
Power *р2;
П1++;
р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 = (с1+)Л(п+1)
if (s==l) (p+i)->n++;
else // оператор уничтожения, [cl,с2+]_+ = 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 << "0"; else
if (factor != 1) cout << "(" << factor « ")*";
for(int i=0; i<m; i++)
--reset() void State-
m>0
.factor^l;
delete
[]p; p:
产null;
ОО
О
О
Fermi(Str^g st) : f(st)
Fermi • -
State Fermi :: operator + (State & s2)
State sl(s2); sl.Fermi»creator(.f ,U ; return si;
State Fermi: :operator - (State & s2)
State sl(s2);
sl.Fermi_creator(f ,-l);
return si;
void main()
{
State g;
Fermi cl ("cl4) , c4(*'c4");
g = Cl- g; cout « Q =
”;g.display();
cout << endl;
§*reset ()j
§ 二 C1+ g.
cout «匕=
“;g-display();
cout
<<
endl;
g,reset()、
? = cl- (cl+
"g =
g);
”;g.displayO ;
cout
<<
endl;
汁 et();
,『(c4+ g))); ;g-display();
cout
<<
endl;
g.reset ();
g = (с4- (с4+ (с4+ (cl+ (с4+ g))))); cout << "g = "; g.display(); cout << endl;
g.reset();
g = c4- (c4+ (c4- (c4+ (c4- (c4+ g))))); cout << "g = ”; g.display(); cout << endl;
g.reset();
g = (c4+ (c4+ (c4+ (c4+ (cl+ g)))));
cout << "g = "; g.display(); cout << endl;
Результат
g = 0
g = cl+I0>
g = lo>
g = o
g = (-1)* 匸4+宀2 cl+|O>
g = lo>
g = cl+ с4+Л4I0>
5・7. Шаблоны классов
Шаблоны обычно используются для реализации структур данных и алгоритмов, не зависящих от типов данных тех объектов, с которыми они оперируют. Это позволяет применять один и тот же код к разным типам, где тнпе действуют как параметры структур данных или алгоритмов. Они называются также параметризованными типами. Одно из важных применений ш疝工① нов — это реализация классов контейнеров^ таких, как стеки, очереди, списк,- и т. д. Шаблон 《стек» может содержать объекты, принадлежащие к 丑“加‘ int, double или даже к определяемым пользователем типам. Компилятор тематически сгенерирует реализации соответствующих классов, отвеча шаблону Stack. Синтаксис объявления этого класса выглядит так:
template <class Т> class Stack
public:
Stack(int);
где символ Т служит аргументом шаблона, обозначающим пропзв。"" Чтобы объявить стек, используем следующий код:
](]00〉〃 I。。 элементов стека типа char
ack^chS〉‘2(50〉// 50 элементов стека типа int
Stack《int late всегда предшествует любому предварительному объ- чеПое сл°в° елению шаблона класса. За ним следует формальный список 黑;認и опРе^^ннтя血 в угловые скобки (< >). Формальный список шабло- ';6ноН°в,за^^Е лустым. Если параметров много, они разделяются запяты- "° например
1 ии т1 class Т2, class ТЗ, class Т4> class Myclassl temple «lass ,
e объявления с указанием конкретного типа, вроде int, на- разрешаются з
пример
、<class Т, int size> class Myclass2
temp丄а。。
тгтлпятой встречает определения объектов, он подставляет вместо когда ком口*1J丄厶丄卜
ся в шаблоне имен типов фактические типы данных. Если в шаблоне используется более одного шаблона, то компилятор будет выполнять ^дстановкн типов по очереди7 начиная с первого упомянутого класса. Для примера рассмотрим упрощенный вариант шаблона класса Vector (полный вариант будет представлен в гл. 6):
// svector.h
Sinclude <iostream.h>
#include <string .h>
#include <assert ,h>
// определение класса Vector
template <class T> class Vector
private:
// поля данных
int size;
T *data;
public:
Конструкторы
Vector。;
;:ct°r(i£);
S:;;:;;:11^ Vectors &);
〈/Мен。®'
operator「] / • 4、
"◎id re4i 丿」(int) const;
// n S12e(int).
口Зратор пр%,
}. 8nst Vec+ ^Исв айв алия ector<T> 办
«operator = (const Vector<T> &);
// реализахщя класса Vector
data(NUgdata(NUgtemplate <class Т> Vector<T>::Vector() : size(0)
data(NUg
data(NUg
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])
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> 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] = datati]; delete [] data;
size = length;
data = newData;
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;
}
i==0;,
f°r ;*加 s;
_i7e. i++) data[i] = v.data[i];
id main。
всеГда должен сопровождаться
〈〃 CM°\薦;в угловых скобках
// типом ^^^5). // порождает вектор целых чисел
Vect' irit :
for for cout
仆-0・ i<5; ++i)x[il = i*i; :二^; i<5; ++i)cout “ x[i] « "" << endl; cout « endl;
Vector<char> ch(3) ; // порождает вектор символов
ch[O]= cout << cout « cout <<
,a3 ch[l] = 'b‘ ; ch[2] = 'c ch[l] « endl;
ch[l]-ch[2] « endl;
endl;
Vector<char *> c(2) ; // порождает вектор строк
c[0] = "aba"; c[l] = "bab";
for (i=0; i<2; i++) cout << c[i] << endl; strcpy(c[O],c[l]);
8ut << c[0] « endl; cout « endl;
Vector<Vector<int> > vec(3);
for (i=0;
vec[0][0] vec ⑴[0] vec ⑵[0]
i<3;
=7;
=4;
=7;
i++) vec[i].resize(4);
vec[0] [1]= vec[l] [1]= vec[2] [1]=
4; vec [0] [2]
9; vec[l] [2]
-3; vec [0] [3] = -17;
0 ; vec [1] [3] = 5;
11 ; vec [2] [2] = 8; vec[2] [3] = 77 ;
产(i=0;
i<3;
i++)
cout « vec[i] [j] << ""cout « vec[i] [j] << ""
cout « vec[i] [j] << ""
cout « vec[i] [j] << ""
产⑴0; j<4; j++) } 8" « endl;」)
cout
« endl;
vec ⑹[°]=
vec⑹⑹
"<< vec[0] [0] << endl;
*vec[2][3] = " << vec[0][0]*vec[2][3] << endl;
Результат
О 1 4 9 16
b
-1
aba
bab
bab
7 4-3 -17
4 9 0 5
7 11 8 77
vec[O] [0] = 7
vec[O] [0]*vec[2] [3] = 539
5.8. Шаблоны функций
В предыдущем разделе мы рассмотрели шаблоны классов, или параметрюо ванные классы. На самом деле функции тоже могут быть параметризованы В шаблоне функции все аргументы, характеризующие тип, должны быть перечислены в аргументах этой функции. Тогда нам не понадобится задавав никаких аргументов шаблона при вызове шаблона функции. Компилятор определит аргументы, характеризующие типы, по фактическим аргументам.
Применением шаблонов функций мы можем определить семейство ро:- ственных функций, сделав параметром сам тип данных. Это увеличит компактность кода, не умаляя никаких преимуществ языка со строгим контр- лем типов. Обычно, чтобы функция могла работать, ее приходится опреМ; для конкретного типа данных. Если нам хочется написать библиотеку м':;; тических программ, включающую функции Бесселя, мы должны опред 価 функции Бесселя для типов int, float, double, long double, complex далее. Если мы хотим усовершенствовать алгоритм на более поздне 曲我 разработки, приходится модифицировать программы вычисления всех /醤 ций Бесселя для каждого типа аргументов. Это утомительная и подвер* 匝屮 ошибкам процедура. Если же определить эти функции как шаблоны.
пилятор автоматически сгенерирует код для каждого типа данных- ^^:
Ключевое слово template всегда предшествует определению и "「由 3aп* тельному объявлению шаблона функции. За ними следует разАелен"" ^巾«" тыми список параметров, заключенный в угловые скобки (< >)■ ^吗以力® называется списком формальных параметров шаблона. Он не может стым. Каждый формальный параметр состоит из ключевого слова с °口pd[ торым следует идентификатор, который может быть встроенным 以 °幺^/严 ленным пользователем типом. Имя формального параметра может ”
IОбмен значений ДВУХ указателей,
рнс. 5・1・
т ЛИшь один раз. Определение функции следует за списком
!; списке парам ^^ Вот пример объявления шаблона функции:
Ермальньгх пар
' late〈class т> Т min(T х,Т у) { . . . } template
ллюстрируем понятие шаблона функции на примере функции, об- "ь【прои значения двух указателей. В первом варианте программы мы ис- "”:з ем указатели, а во втором ——ссылки. Схематическая диаграмма показана
на рис. 5.1-
// Первый вариант // swapl. схх
«include <iostream.h>
template <class T> void swap(T * a,T * b) {
T temp;
temp = *a;
*a = *b;
*b = temp;
『id main()
叩=妇;
gut «_
CQut « у ~ ” ” 必 « endl;//
成《 唸二::P « endl;//
"« &ip « endl;//
:p =%:加 cout
二:"&j « endl;//
5< "&jp 〜,笺 JP « endl;//
&jp « endl;//
0x0003bf8c
0x0003bf8c
0x0003bf88
0x0003bf84
0x0003bf84
0x0003bf80
swap(&ip,&jp);
}
cout << "&i = “ «
&i ip &ip
jp &jp
<< endl;
<< endl;
<< endl;
<< endl;
<< endl;
// 0x0003bf8c // 0x0003bf84 // 0x0003bf88
// 0x0003bf84 // 0x0003bf8c // 0x0003bf80
cout << "ip =" <<
cout « n&ip = ” <<
cout << "&j =" <<
cout « "jp = " «
cout « "&jp = " «
«
endl;
// Второй вариант
// swap2.схх
#include <iostream.h>
template <class T> void swap(T & a,T &
b)
T temp;
temp = a;
a = b;
b = temp;
void main()
int i, *ip;
ip = &i;
cout « ”&i = " «
&i
«
endl;
//
0x0003bf8c
cout << "ip = ” <<
ip
«
endl;
//
0x0003bf8c
cout << n&ip 二"<<
&ip
«
endl;
//
0x0003bf88
int j, *jp;
jp = &j;
cout << "&j = ” <<
&j
«
endl;
//
0x0003bf84
cout « "jp = " «
jp
«
endl;
//
0x0003bf84
cout << n&jp =" <<
&jp
«
endl;
//
0x0003bf80
swapdp, jp);
cout << "&i =" <<
&i
«
endl;
//
0x0003bf8c
cout << 11 ip 二"«
ip
«
endl;
//
0x0003bf84
cout « "&ip = ” <<
&ip
«
endl;
〃
0x0003bf88
известно, что лишь методы имеют доступ кизвестно, что лишь методы имеют доступ кQ ДРУ WeHBOCTb
известно, что лишь методы имеют доступ к
известно, что лишь методы имеют доступ к
宀 обсуждении нам
由甘吧喘„ям класса. Иногда это правило становится слишком ограничи- закрь"НМ 上羅ктвннм. в таких случаях нам может захотеться позволить течьным и He。©? являющейся методом класса, обращаться к закрытым дан- н;кМ ФУнКЦЙЙ,“но 9Toго можно добиться, объявив такую функцию другом непосредствен 口 •
н этого класса.
(friend丿二X — ато такая функция (или класс), которая, хотя она и
Ппуг F класса 宀
^‘ методом X, имеет полные права доступа к закрытым и защи- не является ^асса X. Во всех прочих отношениях F —это нормальная w°h:"2(или класс) во всем, что касается области видимости, объявлений ф'нк“и^ 攻诉 Отношение дружественности классов не транзитивно: из того :W X является другом Y, a Y —другом Z, не следует, что X является другом Z Однако дружественность может быть унаследована. Как и методы, дружественная функция явным образом указывается в объявлении класса. Ключевое
слово friend является спецификатором объявления функции. Оно имеет еле-
лующий общий вид:
friend возвращаемый-mun имя-функции ( список параметров )
Для примера рассмотрим оператор потокового вывода в нашем классе String. Заметим, что он объявлен как friend в объявлении класса. Это значит, что } казанная функция имеет доступ к закрытым данным этого класса.
class String
friend ostreamfe operator << (ostreamfe,const Stringfe);
{^& operator << (osream& out,const String& s)
s.data;
} return out;
尢 Функция no
0nH(0nH(:'аанньхх & d&;e也ает содержимое строки в выходной поток. Заметим, что по- *改ественноЗ: На самом деле относится к закрытым данным класса. Именно в г*м ааннь【м в"。? Функции данному классу сделала возможным доступ к -「:°6ально屯 口е "°访ствительности мы уже пользовались дружественностью
0nH(
0nH(
'ано в раз" ;;РУЗке операции конкатенации в классе String, как было
Рассмотрим еще один пример. Пусть мы хотим пост класс комплексных чисел Complex (его полный вариант буд^ в гл. 6). Вместо того чтобы определять add() и display() 值 呱 объявим их как дружественные функции класса Complex. Me // 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(0.0) {}
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;
result.imag = c1.imag + c2.imag; return result;
void display(Complex c)
cout << " (*' << c.real << " + i*" << c. imag << ")";
void main()
Complex a(1.2, 3.5), b(3.1, 2.7), c;
c = add(a, b);
display (a); cout << “ + ”; display (b); cout << '* = ”; display(c);
}
Результат
(1.2 + i*3.5) + (3.1 + i*2.7) = (4.3 + i*6.2)
кование
即飙映 оЩНЫЙ механизм повторного использования кода. Оно 5'"' аяме — зт。 систематизировать родственные классы на основании 吧:爲吧需механизм порождения новых классов из старых. Напри- :^°^ свОйстб. Это льник и Квадрат могут быть объединены в более об- iwiW"' ассН КрУ「' ° ие фигура. Экземпляры класса Фигура обладают ря- 吧.力P吧閣} таких, как площадь, периметр и т. д. Круг, Треугольник и “"冶 06mli* свойст , 母 н производные классы) класса Фигура, тогда как ':и:тат — зт0 п°А (или базовый класс). Производные классы наследуют 切rypa—зт 口吋 иональные особенности базового класса. С помощью насле- сво^ства 瓜 ФуН ^ь1ТЬ вь1Строена иерархия, позволяющая использовать один и дования м°*裏терфе^& Общая форма подкласса, производного от существу- iro Snow класса, это
1Mtя класса : [ public/protected/private ] имл-базового-класса
};
Кичевые слова public, protected и private описывают, как производный класс наследует информацию от базового класса. Они позволяют контролировать, в каком качестве данные и методы надкласса входят в подкласс:
открытое (public) наследование: если надкласс наследуется открыто, то доступ ко всем данным и методам надкласса остается в подклассе неизменным,
защищенное (protected) наследование: при этом виде наследования все открытые данные и методы надкласса становятся в его подклассе защищенными,
закрытое (private) наследование: всякий раз, когда надкласс наследуется закрыто, все открытые и защищенные данные и методы надкласса стано- закРЬ1тыми в его подклассе. Следовательно, внутренние данные и ^^1 надкласса недоступны подклассу.
СлеДовадияЮ111'е^ таблице подытожено поведение этих трех разных видов на-
■“ наследование Открыто;
Доступ в надклассе => Доступ в подклассе
открытые члены => открытые члены защищенные члены => защищенные члены закрытые члены => закрытые члены
открытые члены => защищенные члены защищенные члены => защищенные члены закрытые члены => закрытые члены
открытые члены 今 закрытые члены защищенные члены => закрытые члены закрытые члены => закрытые члены
уразумеваются данные и методы класса.
Рис. 5.2. Схематичная диаграмма наследования среди классов угольник» и «Круг».Рис. 5.2. Схематичная диаграмма наследования среди классов угольник» и «Круг».«Фигура», iT^«Фигура», iT^
Рис. 5.2. Схематичная диаграмма наследования среди классов угольник» и «Круг».
Рис. 5.2. Схематичная диаграмма наследования среди классов угольник» и «Круг».
«Фигура», iT^
«Фигура», iT^
Открытое наследование соответствует соотношению является^ т. е. X 企&翅 ется Y (класс X — производный класс базового класса Y). Оно использует^ когда наследование служит частью интерфейса. Например, описанное выц] наследование, относящееся к геометрическим фигурам, это открытое наследование, поскольку Круг ——это Фигура, Треугольник — это тоже Фигура, и щ далее.
Защищенное и закрытое наследование имеют иной смысл. Защищенное наследование используется, когда наследование является частью интерфейса для производного класса, но не частью интерфейса, предназначенного для пользователей. Что же касается закрытого наследования, то оно используется, когда наследование вообще не является частью интерфейса, а лишь особенностью
реализации.
В приведенной ниже программе базовый класс Фигура(ЗЬаре) имеет за щищенные данные площадь (area) и открытый метод print_Area()"' напечатать_Площадь). Классы Треугольник и Круг являются подкласса^ класса Фигура (рис. 5.2). Поскольку подкласс Треугольник наследует °^_ то, то открытый метод print_Area() остается открытым. Поэтому его вызывать из основной программы. Однако подкласс Круг наследует за так что в этом классе метод print_Area() становится закрытым. Следов^^. но, попытка вызвать этот метод из основной программы приводит к компиляции.
// shape1.схх
#include <iostream.h>
const double PI = 3.14159265;
class Shape {
储.+ Area。const { cout « area « endl; }
void Shape-・P
*• public Shape
class Triage ■ P
"K;;e base, height;
P\;;2gle(double, double);
base(b), height(h)base(b), height(h)};
base(b), height(h)
base(b), height(h)
Triangle: :Triangle (double b, double h) { area = 0.5*b*h; }
class Circle : private Shape
private:
double radius;
public:
Circle(double);
};
Circle:: Circle (double r) : radius (r) { area = PI*radius*radius; }
Triangle t(2.5,6.0); t;print_Area(); //Bela r(3.5); r • Print_Araa(); // //Triangle t(2.5,6.0); t;print_Area(); //Bela r(3.5); r • Print_Araa(); // //void main() {
Triangle t(2.5,6.0); t;print_Area(); //
Bela r(3.5); r • Print_Araa(); // //
Triangle t(2.5,6.0); t;print_Area(); //
Bela r(3.5); r • Print_Araa(); // //
7.5
функция "Shape::print_Area" недоступна r.print一Area();
:黑0^收 сцст^覺зевание В C++ реализуется виртуальными функциями. Это ° ^енид £ вьтбирать нужный метод для конкретного класса во время от'Мобью клюнем”15' Виртуальные функции определяются в базовом классе *€日。Иди 口 ВОГо Вова virtual. Определение этих функций может быть гнорировано в любом подклассе. Если производный класс
не дает своей собственной реализации, то будет использовано определени базового класса. е ;
Рассмотрим тот же пример, что и в разд. 4.4. Иерархия наследован строится с использованием Фигура как абстрактного базового класса а ®: и Треугольник — как производных классов (рис. 5.3). Виртуальная функц; Calculate_Area() (Вычислить_Площадь()) объявляется в абстрактном базовс- классе Фигура. Однако ее реализация откладывается и переопределяется в ег, производных классах, в которых используются конкретные формулы для вычисления площади геометрических фигур.
// shape2.схх
#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
height;height;base,base,rivate* Rouble Irivate* Rouble Ihie double);匿:嚣-Ai() const;
height;
height;
base,
base,
rivate* Rouble I
rivate* Rouble I
P TC^g jdoubleP TC^g jdoublecie::circle(double r) : radius(r) {}
P TC^g jdouble
P TC^g jdouble
5 rircle::Calculate_Area() const
"uble pI*radius*radius; }
{ return F丄
le--Triangle (double b, double h) : base(b) , height (h) {}
doubledoubleTriangle: :Calculate_Area() const 0.5*base*height; }
double
double
{ return{ returnvoid mainO
{ return
{ return
Shape *s;Circle c(5);Triangle t(3,8);Shape *s;Circle c(5);Triangle t(3,8);s = &c; cout « s = &t; cout «}s = &c; cout « s = &t; cout «}// s указывает на Circle с
Shape *s;
Circle c(5);
Triangle t(3,8);
Shape *s;
Circle c(5);
Triangle t(3,8);
s = &c; cout « s = &t; cout «
}
s = &c; cout « s = &t; cout «
}
,,s->Calculate_Area()二"<< s->Calculate_Area() << endl;
// s указывает на Triangle t
"s->Calculate_Area()工 ” << s->Calculate_Area() << endl;
Результат
s->Calculate_Area() = 78.5398 alculate_Area() = 12
ва^тся в)^УЛЬТата виано, что подходящая функция Calculate_Area() вызы- т垃 программь1 ^СТи от тппа объекта, на который указывает s, во время рабо- '紀н«я. ■ ' форма полиморфизма называется полиморфизмом вклю-
Обертывающий класс
Pb; к® C++ разли
"Рнло也енИах чак)ТСя основные и абстрактные типы данных. В некото- тный Это цп ЫВаеТ поле3но трактовать основной тип данных как аб- Мер, в ЯЗЬ1Ке j дит к концепции, называемой обертывающим классом. 6°ubi "ньх В Зе 'V' ИМеются обертывающие классы для всех основных ти- "Реобразу п^еи программе мы покажем, как основной тип данных °' в абстрактный тип данных Double.
// doublec.схх
#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 double() 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 [] p; }
Double::operator double() const { return value; }
void main()
Double *sl, s2(5.23);
si = new Double(3.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」3・ Библиотека стандартных шаблонов
Введение不
Библиотека стандартных шаблонов, или БСШ, это имеющаяся в Q0^^'' блиотека контейнерных классов, алгоритмов и итераторов [1]- °口:円№紋"“ много основных алгоритмов и структур данных, известных в инФ 匕他 6川! Назначение БСШ — обеспечить набор контейнерных классов, к°‘°Р
НЬ1 и удобны. Наличие такой библиотеки упрощает е^енно эфФ рамм, а поскольку такая библиотека стандартна, то 6N °炉。% сЛ。*111^ " 1е на ее основе программы будут обладать высо- )С ости Этот раздел посвящен новой стандартной вер- ен認анне БеШ. скорее всего, сделает программное обеспе- №用患口 0споль^^ бо^ее переносимым и более общим, а также снизит '""6。工既 наДеЖН1^^т ОдИН из наиболее интересных аспектов БСШ — "“",[駿 以° Р33 ^ структуры от структуры почти всех ранее написан- ^^^^ьное °т"3 кольку C++ — объектно-ориентированный язык, эти бо- 吸6М吧二囁шИроко использовали объектно-ориентированные ме- :图 ранние ип ^едОвания. в БСШ наследование почти не применяется. Что- тоДь1, вро^е на этого не-объектно-ориентированного подхода, отметим, что в бы понять смь ти анном программировании инкапсуляция считается основ- МектнвоР% 朋хорошо сконструированного объекта мы должны стараться ной не〃ью・ А все СОСТояния и способы поведения, необходимые для выпол- ИИКаПСс^щей перед ним задачи, и в то же время скрыть как можно боль- н‘н"Я обностей его внутреннего устройства. Почти во всех предшествующих ^ъектно-ориентированных библиотеках контейнерных классов эта философия проявлялась в виде наборов классов с избыточно богатыми функциональными возможностями, а значит, и с обширными интерфейсами и сложными реализациями. Разработчики БСШ избрали совсем иной путь. Каждый компонент предназначен работать совместно с богатым набором типовых алгоритмов. Эти типовые алгоритмы не зависят от контейнеров и поэтому способны оперировать с большим разнообразием типов контейнеров. Отделив функциональные возможности типовых алгоритмов от самих контейнерных классов, БСШ ре- алнзует значительную экономию размера как самой библиотеки, так и сгенерированного кода. Здесь нет дублирования алгоритмов в каждом из примерно лесятка различных контейнерных классов, так как одно и то же определение тогоИОТеЧН°^ б'нк"™ может использоваться с любым контейнером. Кроме 叩心史:‘叨'"。"11' этих Функций обладают такой общностью, что их можно заннхх% * °6ы'ным массивам языка С и указателям, а также к иным типам иуж^т У^вс^^ тип Данных bool также реализован в БСШ. Ядром БСШ Р" Фундаментальные конструкции:
J- к°нтейнеры) ■ ^г°Ритмы3 нтератор*
^'екть% 国^:网1'1111 Работают совместно друг с другом. Контейнеры — это Милнеров.。牙已 ^РУгие объекты. Алгоритмы действуют на содержимое е; "еребнрать ^РатоРы — это объекты, служащие указателями. Они позволя- У^атепн дда Роимое контейнера таким же образом, каким мы применя-
Sgo【it匾 "еребора массива. Классы, имеющиеся в БСШ, суть deque f .
n皿ericn皿ericqueuequeueset stack utility vectorset stack utility vector证电证unctionai iterator list map
n皿eric
n皿eric
queue
queue
set stack utility vector
set stack utility vector
Концепция пространства имен
Пространства имен — это недавнее добавление в C++. Прост здает область объявления, в которой можно разместить paaTu^'TR。咏僅 программы. Элементы, объявленные в одном пространстве нм^"® 汩е; элементов, объявленных в другом пространстве имен. Строка usin^^%'J std сообщает компилятору, что нужно использовать пространств^
// namesp.срр
#include <iostream>
using namespace std;
namespace A { int i = 10; }
namespace В { int i = 5; }
void fA()
using namespace A;
cout << HIn f A: ” << A: : i << “ ” << В: : I << " " <<< i « endl;
// => 10 5 10'
}
void £B()
using namespace В;
cout « "In fB: ” « A: :i « “ *' « В: :i « " ” « i « endl; 〃 => 10 5 5
void main()
£A(); fB();
cout « A: :i << " 11 << В: :i << endl; // => 10 5 using A::i;
cout « i << endl; // => 10
5・13・3・ Класс vector
Класс векторов ——это контейнерный класс. Он поддерживает 厶况日 к » массив, т. е. массив, могухций расти по мере надобности. Для 耳℃"口 элементам можно использовать стандартные индексные обозначения ^©,七“
Конструкции и методы (функции-члены), используемые в к^аС перечислены ниже.
5,13,
Екбпкитека стандартных шаблонов165
炉С吗p V :
T> v(i*.
М”v(aVector)‘
цементам
доступ К
V front:
V.back。
конструктор по умолчанию
инициализируется явным заданием размерности размерность и начальное значение конструктор копирования
доступ по индексу, возможо присваивание
первое значение в наборе
последнее значение в наборе
В噹 back(T) 、 :%^⑴ era*,value) “svap(vector<T>)
добавить элемент в хвост вектора вставить новый элемент после итератора обменять значения с другим вектором
удалить последний элемент вектора удалить один элементудалить последний элемент вектора удалить один элементудаление
удалить последний элемент вектора удалить один элемент
удалить последний элемент вектора удалить один элемент
V.pop back。
v erase (iterator)
РазмерV.capacity ()V.size()九 resize (unsigned, Т) v. reserve (unsigned)v.empty()РазмерV.capacity ()V.size()九 resize (unsigned, Т) v. reserve (unsigned)v.empty()” eraseCiterator,iterator) удалить ряд последовательных значений
Размер
V.capacity ()
V.size()
九 resize (unsigned, Т) v. reserve (unsigned)
v.empty()
Размер
V.capacity ()
V.size()
九 resize (unsigned, Т) v. reserve (unsigned)
v.empty()
наибольшее число элементов в буфере текущее число элементов в буфере изменить размерность до указанного значения задать физический размер буфера истина, если вектор пуст
Итераторыvector<T>: iterator itrv-begin ()Lend ()Итераторыvector<T>: iterator itrv-begin ()Lend ()объявить новый итератор начать перебор закончить переборобъявить новый итератор начать перебор закончить перебор
Итераторы
vector<T>: iterator itr
v-begin ()
Lend ()
Итераторы
vector<T>: iterator itr
v-begin ()
Lend ()
объявить новый итератор начать перебор закончить перебор
объявить новый итератор начать перебор закончить перебор
//Mvector.cpp
include ^includeinclude ^include5叱5叱<iostream>〈vector〉<iostream>〈vector〉namespacenamespacestd;std;
include ^include
include ^include
5叱
5叱
<iostream>
〈vector〉
<iostream>
〈vector〉
namespace
namespace
std;
std;
产 та”。
w(4)
w[口 =
Y ; w⑵='+ ' ; w[3] = ?
<< endl; // => +
j++)
cout « "w[" << j « "] = " << w[j] << endl;
vector<double> v;
double x;
cout << "ввести число типа "double", за которым
while(cin » x, x != 0.0)
ТИПа "double”ТИПа "double”// в хвост вектора v// в хвост вектора vv.push_back(x);// добавляет значение x
ТИПа "double”
ТИПа "double”
// в хвост вектора v
// в хвост вектора v
vector<double>::iterator i;
for(i=v.begin(); i != v.end(); ++i)
cout << *i « endl; // разыменование
bool b = v.empty();
cout « "b = " « b << endl;
// вставить элемент в вектор
double у;
cout << "ввести вставляемое значение"
cin >> у;
vector<double>::iterator р = v.begin();
Р += 2;
v.insert(р,4,у);// указать на третий элемент,
// вставить четыре элемента
for(i=v.begin(); i != v.end(); ++i) cout << *i << endl;
int length = v.sizeO ;
cout << "размерность = " << length << endl;
return 0;
Мы читаем текстовый файл строка за строкой, пока не встретим к"宙忆 слово eof. Единственная загвоздка в том, что нельзя применять °пер&:°Р токового ввода >> string. Эта функция читает только слово, а не всю Решение состоит в применении глобального оператора
getline(istreamfe,string)
Пусть наш текстовый файл (файл ASCII) называется william.txt, и °" жит следующее:
to be or not to be
После компиляции и сборки исполняемого файла мы вводим в к°^1ай^ ке следующее:
0dmi *"P0dmi *"P;/re(;/re(时cl噥 #ide #邱呼 include时cl噥 #ide #邱呼 include<iostreain>《string〉<vector^<iostreain>《string〉<vector^usingusingnamespace std;namespace std;七 argcQat *argv口){ ifstream ifs(argv[U)vector〈string〉 v;string s;七 argcQat *argv口){ ifstream ifs(argv[U)vector〈string〉 v;string s;wbile(getline(if s, s))v .puch_back(s); cout « s <<endl;}wbile(getline(if s, s))v .puch_back(s); cout « s <<endl;}cout << "v[l] = " << v[lj << endl; // => not to becout << "v[l] = " << v[lj << endl; // => not to bereturn 0;return 0;//bubblest, cpp//bubblest, cpp^include ^include ^include #/0ude^include ^include ^include #/0udeusingusing<^lgorithm>〈vector〉 <stdlib,h> <^ostream.h><^lgorithm>〈vector〉 <stdlib,h> <^ostream.h>namespace std;namespace std;
0dmi *"P
0dmi *"P
;/re(
;/re(
时cl噥 #ide #邱呼 include
时cl噥 #ide #邱呼 include
<iostreain>
《string〉
<vector^
<iostreain>
《string〉
<vector^
using
using
namespace std;
namespace std;
七 argcQat *argv口)
{ ifstream ifs(argv[U)
vector〈string〉 v;
string s;
七 argcQat *argv口)
{ ifstream ifs(argv[U)
vector〈string〉 v;
string s;
wbile(getline(if s, s))
v .puch_back(s); cout « s <<endl;
}
wbile(getline(if s, s))
v .puch_back(s); cout « s <<endl;
}
cout << "v[l] = " << v[lj << endl; // => not to be
cout << "v[l] = " << v[lj << endl; // => not to be
return 0;
return 0;
//bubblest, cpp
//bubblest, cpp
^include ^include ^include #/0ude
^include ^include ^include #/0ude
using
using
<^lgorithm>
〈vector〉 <stdlib,h> <^ostream.h>
<^lgorithm>
〈vector〉 <stdlib,h> <^ostream.h>
namespace std;
namespace std;
Тетр]*® «с]д D
Sid bubbles $' Rail<iomAccessIterator>
^doa^c?rt(RandQmAccess工terator first,
;"essiterator last)
:exchanged;
(Rarz 0,last —
J-^erator iter=f irst; iter < last; i
if(*iter > *(iter+1))
{
iter_swap(iter,iter+1);
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.beginO,data.end());
// после сортировки
for(n=0; n<nData; n++) cout << data[n] << ” ”;
Класс List
Класс списков ——это тоже контейнерный класс. Связанный список^это й структура данных, которую мы выбираем, когда число элементов в наборе яе может быть ограничено или сильно варьируется в процессе работы ир°「Р& Подобно классу векторов, класс связанных списков содержит значения 篙职 типа. Списки не индексируются. Ниже перечислены конструкторы и этого класса.
Конструкторы и присваивание
list<T> 1;
list<T> l(aList)
= aList
1.swap (aList)
конструктор по умолчанию конструктор копирования присваивание
обмен значениями с другим
списком
первый элемент в списке последний элемент в спискепервый элемент в списке последний элемент в списке
первый элемент в списке последний элемент в списке
первый элемент в списке последний элемент в списке
Доступ к элементу 1.front() l.backO
каление
rk(value) a * ”or,value) (iterate,
011t О
Lpop 亠一
Lpop-back О erase (iterator)
L erase (i terator, i terror) remove(value) remove^if (predicate)
1
добавить добавить вставить удалить : удалить : удалить ; удалить ] удалить ] удалить ]
значение
значение
значение
к
к
в
значение из
значение из
началу концу I
списка
списка
указанное место начала списка
начала списка
указанный элемент
ряд элементов
все вхождения значения все значения,
размер 1.empty Lsize
истина, если список пуст возвращает число элементов в списке
Итераторы list^-: iterator itr
1. begin ()
I.endO
l.rbegin
1.rend
объявить новый итератор начать перебор закончить перебор
начать обратный перебор закончить обратный перебор
удов ле тв ор яющие условию
1
Прочее1.reverse()1. sort()L sort (comparison)1.merge(list)Прочее1.reverse()1. sort()L sort (comparison)1.merge(list)
Прочее
1.reverse()
1. sort()
L sort (comparison)
1.merge(list)
Прочее
1.reverse()
1. sort()
L sort (comparison)
1.merge(list)
обратить порядок следования элементов разместить элементы в восходящем порядке упорядочить элементы с помощью сравнения соединить с другим упорядоченным списком
В приведенной ниже программе показано использование класса List. 〃mlist.cpp
«include <iostream> #include <list> ♦include <strin.g> using namespace std;
void main(vuid)
iist<char> 1st;
int i;
ford。; i<10; i++)
^ sizeO “ e^工; cwt « "размер списка.二心 <<叮 cout « *'содержимое списка.. ,»口();
^-ist<char> : : iterator p 二 ist^eg
while(р != 1st.end()) { cout << *р << 1st.push_back(,А,); 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.end()) { cout << *q << " "; q++; }
Класс Stack
Класс стек представляет собой контейнерный класс с ограниченной функциональностью. По умолчанию тип данных этого контейнера—vector. Это структура данных «последним вошел — первым вышел». Класс Stack не допускает итеративного перебора своих элементов. Это набор элементов данных, расположенных в линейную последовательность, вместе со следующими пятью операциями:
CreateStack создает стек.
MakeStackEmpty удаляет из стека все содержащиеся в нем данные.
Push добавляет элемент с одного конца стека, называемого вершиной сте";
Pop удаляет элемент, находящийся в вершине стека, и делает этот элейен доступным для использования.
StacklsEmpty проверяет, содержит ли стек какие-либо элементы. Элементами стека могут служить целые числа, вещественные числа, знак абстрактные типы данных. Шаблон стека имеет два аргумента:
stack<T,vector<T> > s;
Ниже перечислены операции, предоставляемые БСШ для класса Stack-
Вставка и удаление
s.push(value)помещает значение ввершинустека
s.top()доступ к значению ввершинестека
s.popудаляет значение извершиныстека
СразЗ 父
>=
//
聯餐旷…
показано одно простое применение класса Stack.
include <iostream> ^include <vector> #include <stack> using namespace std,
int mainO
stack<char,vector<char> > S, T, U;
cout << "&S = ” << &S << endl;// => адреса
S.push('X');
S,push(,Y‘);
pushLZ');
cout << "символы, размещенные в S: X Y Z\n";
8ut « "размер S = " « S.size() « endl; 〃 => 3
T = S;
;:";"после T = S; мы имеем ";
(S == т ? “s == T" : "S != T") « endl; // => S == T
u-push(Jxj) •
cout cout coutcout cout cout“ "разме]<< "тепер:<< (S < и“ "разме]<< "тепер:<< (S < иU・push( W) •
cout cout cout
cout cout cout
“ "разме]
<< "тепер:
<< (S < и
“ "разме]
<< "тепер:
<< (S < и
Imo в U: X W\n";
,выведем ";
bg] coutbg] coutЪ = (T == « "b =..Ъ = (T == « "b =..? "S < U" : "S >= U") « endl; // => S >= U
bg] cout
bg] cout
Ъ = (T == « "b =..
Ъ = (T == « "b =..
S);
<< b << endl; // => 1 (true)
} ^比n1 о;
Класс Queue
Очередь—это структура данных «первым вошел — первым выще наст, что элементы добавляются в хвост очереди и могут 681Tb % ^то и ее головы. Очередь — это адаптер контейнера, т. е. она реализуете ^^^ih ' коего лежащего в ее основе контейнерного типа. Очередь не л Я 口°灰伙; ративного перебора ее элементов. Очередь — это набор элементов 旷口侬^ ; ганизованных в линейную последовательность, вместе со следую "'^口七区令 операциями:f Мн 口冷:
CreateQueue создает очередь.
MakeQueueEmpty удаляет все содержащиеся в очереди элеменТЬ1
EnQueue добавляет один элемент к одному из концов очепели □
卜 называв, му ее хвостом.
DeQueue удаляет один элемент с другого конца очереди, называ? ловой, и делает этот элемент доступным для использования. °"”
QueuelsEmpty проверяет, не пуста ли очередь.
Шаблон очереди имеет два аргумента
queue<T,list<T> > q;
Ниже перечислены операции, предоставляемые БСШ для класса Queue.
Вставка и удаление
q.push(value) q.front() q.backO q.popO
ставит значение в хвост очереди
доступ к значению в голове очереди доступ к значению в хвосте очереди удаляет? значение из хвоста очереди
Размер q. size() q.emptyO
число элементов в очереди истина, если очередь пуста
В следующей программе показано простое применение класса Queue, // queue1.ерр
#include <iostresini>
#include <list>
#include <queue>
using neimespace std;
int main()
queue<double,list<double> > 0; cout << "&Q = " << &Q << endl;
.14); .5);
Q.PU; Q-Pu; Q-Pu; “ut gut cout
sh(6.
";размещения 3.14, 4.5, 6.9:\n; 口°'"° 二 << Q. front () << endl; iq fron “ << Q.back() << endl; Qback(丿
// //
=> 3.14;
=> 6.9
Q.poP。' осле выполнения Q . pop() : \n";
cout :/ ,Q front。= " << Q・ front () << endl; COUt
…Q size。= " « Q・size() << endl;
queue<double,list<double> > P;
P =J% up.front。= “ « P.frontO « endl;
;::t a nP.back() = " « P-backO « endl;
// => 4.5
// => 2
// => 4.5
// => 6.9
return 0;
5.13.7. Класс Deque
Двусторонняя очередь ——это абстрактный тип данных^ в котором вставки и удаления можно производить с обоих концов очереди. Тем самым она сочетает свойства стека и очереди. На практике такая очередь применяется в двух вариантах. Первый из них ——двусторонняя очередь с ограничением на вставку, в которую можно вставлять элементы лишь с одного конца, а второй —дву- '「оронняя очередь с ограничением на удаление, в которой изъятие элементов возможно лишь с одного конца.
Нюке перечислены операции, предоставляемые БСШ для класса двусто- Рмних очередей.
Конструкт。]
deque<T> deque<T> deques
ры и присваивание
d;
d(int);
d(int,value);
d® d :
5:
!que<T>
aDe
sw<
d(aDeque);
QUe;'
конструктор конструктор конструктор и начальным конструктор
по умолчанию
с исходным размером с исходным размером значением
копирования
*Р(⑪ eq*);
"“ТУ口 к 4
d ⑴ Ле^ентаи и
присваивание очереди из другой очереди обмен содержимого с другой очередью
:fnt() d"ack()
•Ку
rator,value)
вставка
доступ по индексу, возможно присваивание первое значение в очереди последнее значение в очереди
вставить значение перед итератором
d.push.front(value) d.push_back(value)Удалениеd.pop_front()d.pop_back()d.erase(iterator)d.erase(iterator,iterator)d.push.front(value) d.push_back(value)Удалениеd.pop_front()d.pop_back()d.erase(iterator)d.erase(iterator,iterator)вставить значение в голову вставить значение в хвост 驳“口飞旺备身
d.push.front(value) d.push_back(value)
Удаление
d.pop_front()
d.pop_back()
d.erase(iterator)
d.erase(iterator,iterator)
d.push.front(value) d.push_back(value)
Удаление
d.pop_front()
d.pop_back()
d.erase(iterator)
d.erase(iterator,iterator)
удалить элемент из головы Ве
удалить элемент из хвоста 3^°^^
удалить один элемент丁°卩@
удалить ряд элементов
Размер d.size() d.empty()
текущее число элементов истина, если вектор пуст
Итераторы
deque<T>::iterator itr d.beginO
d. end () d,rbegin() d.rend()
объявить новый итератор начать перебор закончить перебор
начать обратный перебор закончить обратный перебор
Приведенная ниже небольшая программа показывает применение кла“ Deque.
// dequel.срр
#include <iostresini>
#include <deque> using :namespace std;
int main()
queue<char> D(10,''A'*);
queue<char>::iterator i;
for(i=D.begin(); i != D.end(); ++i)
cout « *i « n "; //=>AAAAAAAAAA cout << endl;
deque<char> E(D);// конструктор копирования
E.push_front(1Z1);
E.push-back(Y);
for(i=E.begin(); i != E.end(); ++i)
cout << *i << " ”; // =>ZAAAAAAAAAAY cout << endl;
5.13・5.13・“ E.size。“ E.size。« endl; // => ° dse)« endl; // => ° dse)endl; // => 12endl; // => 12char coutchar cout"E[丝"“ "С"E[丝"“ "Ссс<< endl; 〃二〉Z<< endl; 〃二〉Zcout Jcout Jсс<< endl; // => А<< endl; // => А
5.13・
5.13・
“ E.size。
“ E.size。
« endl; // => ° dse)
« endl; // => ° dse)
endl; // => 12
endl; // => 12
char cout
char cout
"E[丝"
“ "С
"E[丝"
“ "С
с
с
<< endl; 〃二〉Z
<< endl; 〃二〉Z
cout J
cout J
с
с
<< endl; // => А
<< endl; // => А
return О;
Класс Bit Set
Комб】 si & $1 I 31、3Комб】 si & $1 I 31、3《набор битов» в БСШ реализует побитные операции.
Комб】 si & $1 I 31、
3
Комб】 si & $1 I 31、
3
Конструкторы bitset<N> s
конструирует набор из N битов
bitset<N> s(aBitSet)
конструктор копирования
Побитные операции
s.flipO
инвертирует все биты
s.flip(i)
инвертирует позицию i
s.reset(O)
задает всем битам значение "ложь"
s.reset(i)
задает значение "ложь" позиции i
s.set ()
задает всем битам значение "истина"
s. set ⑴
задает значение "истина" позиции i
s■test(i)
проверяет, имеет ли бит в позиции i
значение "истина"
口перащщ над всем набором
s. any()
возвращает значение "истина", если
s. none()
какой-либо бит имеет значение "истина
возвращает значение "истина", если
s-Count()
все биты имеют значение "ложь"
возвращает число битов,
:Рксваива1ale
имеющих значение "истина"
>инацин с 力
• s2 АРУгими наборами битов
$2побитноеИ
$2побитноевключающее ИЛИ
'$2побитноеисключающее ИЛИ
возвращает значение "истина", если эти два набора совпадают
класса限畸класса限畸u = s & t;cout << nu =u = s & t;cout << nu =<< u << endl;<< u << endl;
класса限畸
класса限畸
u = s & t;
cout << nu =
u = s & t;
cout << nu =
<< u << endl;
<< u << endl;
Другие операции
s << псдвиг набора влево на одну позицию
s >> псдвиг набора вправо на одну позицию
s.to_string возвращает представление набора в вгтп
&е стр%
В следующей небольшой программе показано применение
〃 bitsetl.срр
#include <iostreeini>
#include <bitset> #include <string> using 11amespace std;
int main()
{
const unsigned long n = 32;
bitset<n> s;
cout << s. set () << endl;// задает всем битам значение 1
cout << s.flip(12) << endl; // инвертировать бит в позиции 12 bitset<n> t;
cout << t .reset () << endl; // задает всем битам значение "ложь"
t.set(23);
t.set(27);
bitset<n> u;
bitset<n> v;
v = s I 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;return 0;
}
}
return 0;
return 0;
Библиотека стандартных шаблонов
5.]3・-—--■
K”accpc《множество》определяется через объекты и опе-
Операции в классе «множество»Операции в классе «множество»51'' ь1й тип ^анн ост。множества (конечные) в обычном математиче- \6cTP%力ектн — з'/^ение которых не указано. В БСШ оба класса set и 口а*"" ь1Сле, преДсТа^шь1Х5 являющиеся шаблонными структурами данных, .^.^^-^ ^Ы^^я0на представляет тип элементов, из которых состоит ''jltl пьрс аргУменТ пность. В классе set каждый элемент идентичен сво- ? к;:^тр0ваемая С^чи уникальны. Поэтому два разных элемента множества ”"• клриУ’ и 3a'迈 Ь1 Мультимножество отличается от множества только тем, 噂Л^/ать равные элементы.
Операции в классе «множество»
Операции в классе «множество»
Конструкторыset<T> s;HUltiset<T> m; cet<T> s(aSet),:Met<T> m(aMultiset) s = s5et s.swap(aSet)Конструкторыset<T> s;HUltiset<T> m; cet<T> s(aSet),:Met<T> m(aMultiset) s = s5et s.swap(aSet)、сНО М0*~ ;риведены
Конструкторы
set<T> s;
HUltiset<T> m; cet<T> s(aSet),
:Met<T> m(aMultiset) s = s5et s.swap(aSet)
Конструкторы
set<T> s;
HUltiset<T> m; cet<T> s(aSet),
:Met<T> m(aMultiset) s = s5et s.swap(aSet)
конструктор конструктор конструктор конструкторконструктор конструктор конструктор конструкторпо умолчанию по умолчанию копирования копирования
конструктор конструктор конструктор конструктор
конструктор конструктор конструктор конструктор
присваивание обменивает элементы с множеством? указанным в аргументе
все элементы, указанное значение элемент, указанный итератором последовательность значенийвсе элементы, указанное значение элемент, указанный итератором последовательность значений
все элементы, указанное значение элемент, указанный итератором последовательность значений
все элементы, указанное значение элемент, указанный итератором последовательность значений
Вставка и удаление s. insert (value^type) s. erase (value_type)
s.erase(iterator)
s. erase (iterator, iterator)
вставить новый элемент
удалить имеющие удалить удалить
Проверка s.empty() s-size()Проверка s.empty() s-size()содержимого множествасодержимого множества"истина", если множество пусто число элементов множества подсчитывает число вхождений находит значениепервое вхождение значения следующий элемент после значения нижняя и верхняя грани"истина", если множество пусто число элементов множества подсчитывает число вхождений находит значениепервое вхождение значения следующий элемент после значения нижняя и верхняя грани
Проверка s.empty() s-size()
Проверка s.empty() s-size()
содержимого множества
содержимого множества
"истина", если множество пусто число элементов множества подсчитывает число вхождений находит значение
первое вхождение значения следующий элемент после значения нижняя и верхняя грани
"истина", если множество пусто число элементов множества подсчитывает число вхождений находит значение
первое вхождение значения следующий элемент после значения нижняя и верхняя грани
alytype)
J jnd(value一type)
"а1ие_ type)
-g®(value_type)
” аторн
3鹏(广raWr0 S'eM()
:4 n()
объявить новый итератор
начать перебор
закончить перебор
начать обратный перебор закончить обратный перебор
В следующей программе показано применение класса S
// set2.срр
#include <iostream>
#include <set> using 11amespace std;
int main()
set<int> s;
s.insert(23);
s.insert(45);
s.insert(-1);
s.insert(-2);
s.insert(23);
s.insert(51);
cout <<s.empty() << endl;//=>0(false)
cout <<s.size() << endl;//=>5
cout <<s.count(23) << endl;//=>1
set<int>::iterator i = s.begin();
for(i=s.begin(); i != s.end(); ++i) cout << *i << "";
cout << endl;
s.erase(45);
for(i=s.begin(); i != s.end(); ++i)
cout << *i << " ”;
cout << endl;
cout << s.size() << endl; // => 4
return 0;
}
„ 10. К""' “ (называемый также словарем или таблицей) — это ин- & 0Т0бра"ениЯ ^во> Значения индексов — необязательно целые числа: K】acJ рванное М^ 匕 лЮ5ые упорядоченные значения данных. Поэтому ото- 3t'КСМР&М0 могут ь ность пар ключ—значение. Для отображений никакие ""'[енне-3To °°彳 быть равны. Мультиотображения отличаются от отобра- 6р& клю^а не то в них допускаются повторяющиеся ключи. Ниже пере- •5®ний лиШь те^ операции для класса отображений.
:„^ены возмог
КОНСТРУКТОР111К /Т1Л2> 叫 嗡:imap<Tl,T2> m;額;m;P<Tl,T2> m(aMultiMap)m = aMapКОНСТРУКТОР111К /Т1Л2> 叫 嗡:imap<Tl,T2> m;額;m;P<Tl,T2> m(aMultiMap)m = aMapконструктор конструктор конструктор конструкторконструктор конструктор конструктор конструкторпо умолчанию по умолчанию копирования копированияпо умолчанию по умолчанию копирования копированияприсваиваниеприсваивание
КОНСТРУКТОР111
К /Т1Л2> 叫 嗡:imap<Tl,T2> m;
額;m;P<Tl,T2> m(aMultiMap)
m = aMap
КОНСТРУКТОР111
К /Т1Л2> 叫 嗡:imap<Tl,T2> m;
額;m;P<Tl,T2> m(aMultiMap)
m = aMap
конструктор конструктор конструктор конструктор
конструктор конструктор конструктор конструктор
по умолчанию по умолчанию копирования копирования
по умолчанию по умолчанию копирования копирования
присваивание
присваивание
Вставка и удаление /key] ш. insert (value_type) m. erase (key) m. erase (iterator)Вставка и удаление /key] ш. insert (value_type) m. erase (key) m. erase (iterator)возвращает ссылку на значение с ключом вставляет данную пару ключ-значение} удаляет значение с данным ключом удаляет значение, указанное итераторомвозвращает ссылку на значение с ключом вставляет данную пару ключ-значение} удаляет значение с данным ключом удаляет значение, указанное итераторомПроверка содержимого m. empty ()m.sizeC)m. count (key) m.find(key) m.lower_bound(key) m.upper_bound(key) m.equal_range(key)Проверка содержимого m. empty ()m.sizeC)m. count (key) m.find(key) m.lower_bound(key) m.upper_bound(key) m.equal_range(key)совокупности"истина1', если совокупность пуставозвращает размер совокупности подсчитывает число элементов с данным ключом находит элемент по данному ключу первое вхождение ключаследующий элемент после ключанижняя и верхняя гранисовокупности"истина1', если совокупность пуставозвращает размер совокупности подсчитывает число элементов с данным ключом находит элемент по данному ключу первое вхождение ключаследующий элемент после ключанижняя и верхняя граниИтераторыmap<T>: Orator itr-m・begin()'m.end()Итераторыmap<T>: Orator itr-m・begin()'m.end()m,rbegih() m.rend()В ^едую^ "^ap.cppm,rbegih() m.rend()В ^едую^ "^ap.cppобъявить новый итераторначать переборзакончить переборначать обратный перебор закончить обратный переборобъявить новый итераторначать переборзакончить переборначать обратный перебор закончить обратный переборДвух программах показано применение класса Мар.Двух программах показано применение класса Мар.
Вставка и удаление /key] ш. insert (value_type) m. erase (key) m. erase (iterator)
Вставка и удаление /key] ш. insert (value_type) m. erase (key) m. erase (iterator)
возвращает ссылку на значение с ключом вставляет данную пару ключ-значение} удаляет значение с данным ключом удаляет значение, указанное итератором
возвращает ссылку на значение с ключом вставляет данную пару ключ-значение} удаляет значение с данным ключом удаляет значение, указанное итератором
Проверка содержимого m. empty ()
m.sizeC)
m. count (key) m.find(key) m.lower_bound(key) m.upper_bound(key) m.equal_range(key)
Проверка содержимого m. empty ()
m.sizeC)
m. count (key) m.find(key) m.lower_bound(key) m.upper_bound(key) m.equal_range(key)
совокупности
"истина1', если совокупность пуста
возвращает размер совокупности подсчитывает число элементов с данным ключом находит элемент по данному ключу первое вхождение ключа
следующий элемент после ключа
нижняя и верхняя грани
совокупности
"истина1', если совокупность пуста
возвращает размер совокупности подсчитывает число элементов с данным ключом находит элемент по данному ключу первое вхождение ключа
следующий элемент после ключа
нижняя и верхняя грани
Итераторы
map<T>: Orator itr-
m・begin()'
m.end()
Итераторы
map<T>: Orator itr-
m・begin()'
m.end()
m,rbegih() m.rend()
В ^едую^ "^ap.cpp
m,rbegih() m.rend()
В ^едую^ "^ap.cpp
объявить новый итератор
начать перебор
закончить перебор
начать обратный перебор закончить обратный перебор
объявить новый итератор
начать перебор
закончить перебор
начать обратный перебор закончить обратный перебор
Двух программах показано применение класса Мар.
Двух программах показано применение класса Мар.
delude :Sde —eludedelude :Sde —elude<8р><8р>n戈n®n戈n®'str i,'str i,•ng>•ng>''Расе std;''Расе std;
delude :Sde —elude
delude :Sde —elude
<8р>
<8р>
n戈n®
n戈n®
'str i,
'str i,
•ng>
•ng>
''Расе std;
''Расе std;
int main()
map<char,int> ml;
int i;
for(i=0; i<26; i++)
ml. insert(pair<char,int>(1A>+i,65+i)); // ASCII таблица
char ch;
cout << '*введите ключ: ”; cin >> ch;
map<char, int>::iterator pl;
// поиск значения данного ключа
pl = ml.find(ch);
if(pl != ml.end())
cout << "номер ASCII в таблице: *' « pl -> second << eudl・ else
cout << "ключа нет в таблице.\n";
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;
са2аГУ::%аМГ 加; :為圖装:加№ словаря "";e(true)
{* "введи слово-,
‘噥 3();
СОМ»
cin”Q") I I (wrd =
cout,丄丄
Cin >> 产 v℃[wrd] = def,
;f(voc. empty。)
“ << endl;
"q")) break;
cout « "словарв exit(O);
cout « "S словаре Cont « voc.sizeO
пуст!" << endlJ
имеется
элементов." << endl;
cout « "поиск слов в словаре" << endl;
while(true)
{
cout << "введи слово: "; cin >> wrd;
if ((wrd — "Q") I I (wrd == "q")) break;
itr 二 voc.find(wrd);
if(itr != voc.endO) cout << itr~>second « endl; else cout « "не найдено" << endl;
°J3.U. Класс Algorithm
hhh I;;:”* действуют на содержимое контейнеров. К ним относятся функ- "哨неров Q "за“н“, сортировки, поиска и преобразования содержимого кон- паннмх Q ①Унк“ии могут также применяться к массивам основных типов
'ти Функции суть:
sort, finK
"飞内也 ,copy, reverse, replace
hhr: программа показывает, как применяются некоторые из этих функ- "殃。&1 ерр
fclude < ]goEhm>
<vect℃
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 << "массив после сортировки: \n";
double* p;
for(p=x; p != x+n; p++)
cout << *p << " ”;
cout << endl;
double y;
cout << значение типа double может быть выбрано сin >> у;
double* q = find(x,x+nsy);
if(q == x+n)
cout « "не найдено\n";
else
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, 10s 20 3 30 3 84 };
int b[5] = { 73 9, 14, 35, 101 };
int c [11];
merge(a,a+6,b,b+5,c);
int i;
for(i=0; i<ll; i++)
cout << "c[" << i << ”] = " << c[i] « endl;
sortsort,rt(v.begi 吗 (w.begin。,rt(v.begi 吗 (w.begin。,v. end。); ,w. end。);,v. end。); ,w. end。);vector<int> z(9); merge® ・begin(),vvector<int> z(9); merge® ・begin(),vend(), w. beginO , w. end() , inserter (z, z. begin)));
sort
sort
,rt(v.begi 吗 (w.begin。
,rt(v.begi 吗 (w.begin。
,v. end。); ,w. end。);
,v. end。); ,w. end。);
vector<int> z(9); merge® ・begin(),v
vector<int> z(9); merge® ・begin(),v
for(i=0; i<9; i++)
计 « "zF" « i « "1 = “ « 2[i] « endl; coui 乙l
return 0;
5.14. Рекурсия
乙 t斗卩соя играет в информатике центральную роль. Например, строка и линей- 屮,由 связанный список суть рекурсивные структуры. Рекурсивная функция — "” такая функция, определение которой включает вызов ее самой. Для рекур- '“н необходимо условие обрыва. Конечно, недопустимо использовать функцию 通п в рекурсивном вызове.
Рассмотрим пять примеров. В первом примере мы покажем, как можно ^изациюТЬ 0K61nle с помощью рекурсии. Второй пример показывает ре- '"'^но найтЛеНИЯ ПОСРедством вычитания и рекурсии. С помощью рекурсии их】ь. не к 殴贯涎 Длину строки. На каждом шаге нам необходимо прове- 沙丿.qoy,\0,^^“сь ли мы уже до конца строки, обозначенного нулевым сим- 门н методы' четвертом примере мы рассматриваем связанный список. Все "дрснн. ИСПОЛЬЗуют рекурсию. Наконец, мы приведем пример взаимной
^。双的 ^УХ чисел
delude
с помощью рекурсии
「signed
<iostream.h>
10;
Signed
g mult(unsigned long a,unsigned long Ъ)
long result = o;
184
Глава 5. Основные средства языка C++
if(a == 0) if(b == 0) if(a == 1) if(Ъ == 1)
return 0; return 0; return Ъ; return a;
else result = mult(a>b-l) + a; return result;
int main()
unsigned long n3 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 Ъ)
unsigned long result = 0;
if(b > a) return 0;
if(b == 0) { cout « инельзя делить на 0"; exit(0); }
if(b == 1) return a;
else
result = divide(а-Ъ,Ъ) + 1;
return result;
int main。
unsigned long n, m;
cout << "введите неотрицательное целое n = ”;
}а определяет длину строки рекурсивно. Строка состоит из
吧驚當мешенной в памяти последовательности символов, оканчиваю- непрерывно Р^^^^^ \,0L Концептуально мы можем представлять себе стро~ щейся ^^рТрОку типа NULL, состоящей лишь из NULL символов, либо как ку -1й^° 辎;0Topьтм следует строка. Такое определение строки описывает ее символ, за структуру данных. Значит, мы можем воспользоваться этим каК ^^нием чтобы запрограммировать основные фувдщи% работающие со строками, рекурсивно.
// recursions. срр
#iuclude <iostream.h>
«include <string.h> // для функции strcpy
int length (char *s)
{
if (*s == J\0J)
return 0;
else
return (1+ length, (s+1));
char* st int rest cout « cout <<char* st int rest cout « cout <<int main()
char* st int rest cout « cout <<
char* st int rest cout « cout <<
° "willi hans";
:length(st);
<< resl; // => 10<< resl; // => 10"Длина строки st равна: endl ;
<< resl; // => 10
<< resl; // => 10
chs*
int : cout CQUt
empty =
”\0";
'ӣ,ength(empty);
end]"a пуст。选 строки равна: " « res2;
〃 => О
char* 7 之 м new
工 null; char [旬; Joli"):
'\ выделение памяти
int res3 = length.(z);
cout << "Длина строки z равна: " << res3; /, delete [] z;
return 0;
}
В следующей программе показано, как можно ре; сок посредством рекурсии.
// rlist .h.
#ifndef RL工ST-HEADER
#define RL工ST_HEADER
#include <assert.h>
template <class T>
class RList
public:
RList();
RList(const RList&);
"RList。;
RList &operator=(const RList&);
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;
用Р用Р40:40:"claSS/const RList<T>& RD :RLlSt<RL.empty;"claSS/const RList<T>& RD :RLlSt<RL.empty;he"he"RLRLhead;head;newnewRLisRList《T>(*RL•tail);t《T>(*RL•tail);
用Р
用Р
40:
40:
"claSS/const RList<T>& RD :RLlSt<
RL.empty;
"claSS/const RList<T>& RD :RLlSt<
RL.empty;
he"
he"
RL
RL
head;
head;
new
new
RLis
RLis
t《T>(*RL•tail);
t《T>(*RL•tail);
1 te <class T> 餌 Pl;»・RList;。
;f(!empty) delete tall;
ate <class T>
;:"<Т> &RList<1,>: :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)
lf ^erapty&&RL. empty) return 1;
if (this == &RL) return 1;
:f(head !二 RL.head) return 0;
「心^m Gtail = *RL.tail);
,:• - Insert(const T fetolnsert)
产(empty)
head = tcT
tail Qlnsart;
} empty=o:e” RList<T>;
fsert insert);
template <class Т>
int RList<T>::Search(const T fetoSearch)
{
if(empty) return 0;
else if(head == toSearch) return 1;
else return tail -> Search(toSearch);
}
template <class T>
int RList<T>::Delete(const T fetoDelete)
{
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)
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)
return empty;
template <class T>
RList<T> *RList<T>::Reverse(RList<T> *RL)
RL 工 s'
靠』'限意
,t“>
*R)
eturn R;
sersdif
Применение этой программы показано ниже.
zrlisteg.cpp
^include <iostream.li>
^include "rlist.h"
int main(void)
?.List<int> L;
mt i;
for(i=l; i,=8; i++) L.Insert(i);
^ist<int>* LX 工 &L;
-out << "Исходный RList есть: “ << endl;
”le(!LX -> Empty。)
:一 LX Head() « >
JX = LX->T&ii();
cm « endl « endl
心st<int>*;p L Reversed);
LL = R;
*"le (1 Lr 、
{~> Empty())
'°Ut У тр
« " "« " "3Head。
« " "
« " "
}Tail。;
c°ut «
endl ~
endl;
cout << "что произошло с исходным списком: "« end' LP = &L;
while(!LP -> Empty());
{
cout << LP -> Head() « J ?;
LP = LP -> TailO ;
cout << endl;
cout « "удалим некоторые элементы: "<< endl;
L.Delete(l);
L.Delete(4);
L.Delete(8);
LP = &L;
while(!LP -> Empty())
{
cout << LP -> Head;
LP = } cout
:LP -> TailO ;
«
endl;
cout
«
"есть ли 3 в списке: " « L.Search.(3) « endl;
cout
<<
"есть ли 4 в списке: " << L.Search.(4) << endl;
return 0;
}
На выходе получаем:
Исходный Rlist есть
12345678
87654321
что произошло с исходным списком:
12345678
удалим некоторые элементы:
2 3 5 6 7
есть ли в списке 3: 1
есть ли в списке 4: 0
В случае взаимной рекурсии у нас имеются две функции, вызываЮП^^ друга. В качестве примера рассмотрим одну реализацию синуса и Используем тождества
sin(①)三 2 sin(①/2) cos(①/2),
cos(①)三 cos2(a;/2) - sin2(a;/2)三 2 cos?(出/2) - 1-0-
и потому рекурсивны. Функция синус
delude 小 eludedelude 小 elude<iostream.h><matb • h><iostream.h><matb • h>double doubledouble doublesine(double.double); cosine(double,double);sine(double.double); cosine(double,double);// опережающее описание// опережающее описание// опережающее описание// опережающее описаниекосинус вву J] для функции косинус у нас есть две возможности. ,11" нкН‘ вторую возможность, в которой косинус вызывает
delude 小 elude
delude 小 elude
<iostream.h>
<matb • h>
<iostream.h>
<matb • h>
double double
double double
sine(double.double); cosine(double,double);
sine(double.double); cosine(double,double);
// опережающее описание
// опережающее описание
// опережающее описание
// опережающее описание
・;gd main。
'double x 二 3.14159;
double eps = 0.001; double rest = sine(x,eps); cout « "rest = " « rest « endl; double res2 = cosine(x,eps);
cout « " res2 = " « res2 « endl;
double sine (double x, double eps)
double s;
if (fabs(x) < eps) { s = x* (1.0 - x*x/6.0); } else s = 2.0*sine(x/2.0,eps)*cosine(x/2.0,eps);
return s;
fuble cosine (double x, double eps)
double c;
el!;a;s* < eps) { c = 1.0 - x*x/2.0; }
、return c- ' *cosine(x/2,eps)*cosine(x/2,eps) - 1.0;
:5・ Заключение
;盯徹главе
С:"9ra^Mbie в a;:片 основные программные конструкции и инструменты, 「'"Укц^аием,кор4ab:е C++. Мы начали с описания указателей и ссылок в н C++, в Toy 曜长 Их можно применять. Были описаны разные кон-
• класс,
конструкторы и деструктор,
преобразование типов,
перегрузка операторов,
классы-шаблоны,
функции-шаблоны;
дружественные функции,
наследование,
виртуальные функции,
библиотека стандартных шаблонов,
рекурсия.
В каждом разделе для иллюстрации приведены примеры. Они показыв возможность поддержки объектно-ориентированного программирования '航
торое включает:
инкапсуляцию,
передачу сообщений,
наследование,
полиморфизм и другое.
С помощью этих свойств, заложенных в языке, программисты могут писать мощные объектно-ориентированные программы.
В следующей главе с помощью C++ будет построен набор полезных классов, в котором будет использовано большинство описанных здесь свойств.
КДЯССЫ компьютерной алгебры
и спа мы начнем основное строительство классов нашей символьной ° н Будут охвачены многие математические структуры, а также неко- сметем^也以© структуры данных информатики. В описании классов мы будем т;器2b5 от простых структур (очень длинные целые, рациональные и т.п.) ,6о〃ее сложным структурам, таким, как вектор, матрица и т. д. Вот полный список классов, представленных в этой главе:
Verylong описывает абстрактный тип данных (АТД) «целые числа» (без верхней и нижней границ значений).
Rational описывает АТД «рациональные числа».
Complex описывает АТД «комплексные числа».
Quaternion описывает АТД «кватернионы».
Derive описывает точное дифференцирование.
Vector описывает структуру данных《вектор».
Matrix описывает структуру данных «матрица».
Array описывает структуру данных «массив».
. String описывает АТД «строка».
::.BitVector описывает АТД «вектор битовых полей».
• List описывает АТД «связанный список».
^"Упот'а' ОПИСЬ1вает АТД «многочлен».
• et описывает АТД «конечное множество».
^。。気总*"。11。класса объясняются основные идеи и теория, лежащие в компоне ' С послеДУюЩИм абстрагированием. Также описываются различные В каждой" классов (поля данных, конструкторы, операторы, методы и т. д.). ЖецИя а Риделе приводятся небольшие примеры. Более развернутые прило- ОУДУТ представлень1 в гл. 8.
6】.к
асе сверхдлинных целых ведение
"в прогр禮"近 Тип Данных реализован непосредственно в большинстве язы 罔5 改естк£Р°В'ни* Однако размеры регистров ЦП компьютера наклады- 7 -1548°「Ранинения на диапазон допустимых значений целых чисел.
ОтОтТак, для 4-байтных целых значения лежат между —231 и 231 — 1, или и ду —2147483648 и 2147483647. Эти границы, как правило^ слишком узк1t ”、 сложных вычислений, в процессе которых появляются очень большие 口:"& жительные и отрицательные числа. Назначение этого раздела — избавиться °
От
От
Нс、Нс、этого ограничения путем построения нового целочисленного типа данных пользуя класс сверхдлинных целых (Verylong Integer).'
Нс、
Нс、
Для того чтобы можно было записывать целые произвольной длины Мь должны иметь способ хранения в памяти цифр целого, не накладывают 口 ограничения на представление целых чисел. Один из возможных путей — йс пользование строки цифр для представления очень длинных целых чисел. В 0T личие от встроенного типа целых, которые должны помещаться в 4 байтя ца мяти, мы в строковом представлении обходим это ограничение. В принципе для строкового представления нет ограничения на длину строки (кроме разм; ра доступной памяти). Как следствие, представлены могут быть целые числа произвольной длины.
Строка, в которой хранится число из класса Verylong, состоит лишь из символов цифр 〉0',...,' 9 L Каждая цифра в строке представляет десятичную цифру числа. Например, строка "123" представляет значение 123 (в десятичной системе счисления). Арифметические операции над числами в этом представлении могут быть выполнены с помощью обычных манипуляций с цифрами числа. Вообще говоря, мы могли бы использовать представление из цифр другой системы счисления (например, двоичной), и достигли бы большей простоты в алгоритмах реализации арифметических операций. Но нам важнее простота в достижении других целей, и поэтому мы выбираем деся
тичное представление.
При определении абстрактного типа данных (АТД) определяется не только представление данных (например, в случае класса Verylong ——это строка символов цифр), но также и операции, которые могут совершаться над данными этого типа. При этом детали реализации представления и операций должны быть скрыты. Пользователю необходимо знать лишь поведекше объектов АТД и внешний интерфейс. Вообще нужно стремиться к полноте функциональности класса при минимальности его интерфейса. Будем придерживаться этого принципа в нашем строительстве класса Verylong. Перечислим основные тре- 5ования к объектам класса Verylong:
целочй- div()・целочй- div()・е должны быть включены некоторые общеупотребительные функции, .ТаК*е後к абсолютная величина abs(), возведение в целую степень рои(), так0е,[сленнь1й квадратный корень sqrt () и деление с двойной точностью
целочй- div()・
целочй- div()・
#Mbi^MHbI#Mbi^MHbIиметь возможность в в ода/вывода чисел из Verylong.
#Mbi^MHbI
#Mbi^MHbI
61 2・ Пол3 данных
' verylong имеет три поля данных: vlstr, vlen, vlsign. Следуя прин- ^"^。匕陋仇mu" информации, поля описаны как private, что делает их недо- ^^ 口 вне класса. Доступ к ним или работа с ними возможны лишь через
” кпии доступа, предусмотренные в классе и имеющиеся в классе операторы. ФУ Кратко опишем каждое поле:
.Переменная vlstr хранит строку символов цифр, которая представляет чИСЛ0 из Verylong. Цифры в строке, в принципе, можно хранить как в прямом порядке, так и в обратном. Мы выбираем обратный порядок, имея в виду облегчение выполнения некоторых манипуляций с числами из Verylong.
Переменная vlen хранит длину числа без учета знакового бита.
Переменная vlsign хранит 0 для положительного числа из Verylong и 1 — для отрицательного.
6.1.3. Конструкторы
В этом разделе мы покажем, как создаются числа из Verylong. Как и для всех типов данных, наиболее простой путь создания — оператор описания. Например,
Verylong х;
9To описание создает переменную с именем х. Этот простой оператор в действительности обращается к конструктору, предопределенному в классе. С помощью конструктора число из Verylong по умолчанию инициализируется значением нуль. При необходимости его переменную можно проинициализировать ненулевым значением. C++ допускает перегрузку конструкторов с многократНыми определениями. Разные конструкторы могут различаться (друг от друга) Разными списками аргументов в операторах описания. В следующем примеРе оператор описания создает новую переменую у, которая инициализируется качением 3:
Verylong у(3);
А^^нм, что данный способ порождает следующую проблему. Что произой- ет 5 °5口 пользователь захочет инициализировать значение, которое превыша- 0^аксим^льное значение для встроенного целого типа? Одно из ее решений —— а_ печить конструктору возможность чтения строки символов цифр как его й ент- При такой реализации можно объявить переменную типа Verylong Инициализировать ее значением любой возможной величины. Например,
Verylong u('*123")Verylong v("1234567890123")Verylong w(”-567890")Verylong u('*123")Verylong v("1234567890123")Verylong w(”-567890")Инициализирует переменную u значением 123.Инициализирует переменную v значением, которое превышает границу встроенного целого типа. Инициализирует переменную и отрицательным значинием.Инициализирует переменную u значением 123.Инициализирует переменную v значением, которое превышает границу встроенного целого типа. Инициализирует переменную и отрицательным значинием.
Verylong u('*123")
Verylong v("1234567890123")
Verylong w(”-567890")
Verylong u('*123")
Verylong v("1234567890123")
Verylong w(”-567890")
Инициализирует переменную u значением 123.
Инициализирует переменную v значением, которое превышает границу встроенного целого типа. Инициализирует переменную и отрицательным значинием.
Инициализирует переменную u значением 123.
Инициализирует переменную v значением, которое превышает границу встроенного целого типа. Инициализирует переменную и отрицательным значинием.
Ниже дается краткое описание конструкторов, доступных в классе:
Verylong(const Char*) берет строку символов цифр как аргумент. Пров^
загружаетсязагружаетсяряется и назначается знак целого, выделяется память и в нее
загружается
загружается
инициализируйинициализируйнепосредственно строка. Если аргумент отсутствует, число ется нулем.
инициализируй
инициализируй
Verylong(int) берет целое как аргумент, преобразует его в строку символов цифр и загружает непосредственно ее.
Конструктор копирования является критичным для класса. Хотя компилятор C++ автоматически генерирует его, если он опущен, сгенерированный конструктор копирования может быть некорректным при использовании динамического распределения памяти, так как в этом случае он просто копировал бы указатели. Этого мы хотели бы избежать. Поэтому лучше определить соответствующий конструктор копирования, чтобы гарантировать корректность копирования во всех ситуациях.
Деструктор просто освобождает память, которая больше не используется.
.1.4. Операторы
'огда мы перемножаем две матрицы А*В, мы используем метод, совершенно гличающийся от умножения двух чисел с плавающей запятой. В C++ ариф- етические операторы могут быть перегружены. Единрхвенное требование — эвое определение не должно быть неоднозначным. Это означает, что опреде- зние не должно требовать аргументов, которые соответствуют другому суще- твующему определению.
В случае класса Verylong, следующие операторы перегружаемы:
++, —, -(унарный),十,-,*, /, %,=,
+=, ~=, *=» /=> %=, ==» <=, <, <<, >>, >= •
пишем функции и алгоритмы, используемые для каждого оператора:
> Оператор присваивания = используется, чтобы присвоить значение числа типа Verylong другому числу. Реализация подобна конструктору копир。" вания, но их функции различны.
Как и для любого другого встроенного типа данных, операторы инкремент" (++) и декремента (--) перегружаются в классе. Однако они могут использоваться в двух разных формах — префиксной и постфиксной. ПерегруЖ3^ эти операторы, нам нужно знать различия между ними. Ниже показать1 обе формы оператора инкремента. Оператор без параметра — для префиКс"
“小0DM3I инкремента и оператор с параметром int — для постфиксной 牒мн инкремента:
class Verylong
Verylong operator ++ (); // префикс: ++Verylong
Verylong operator ++ (int); // постфикс: Verylong++
При осуществлении этих двух функций необходимо помнить, что: /[) В префиксной записи величина изменяется и затем используется.
В постфиксной записи величина используется и затем изменяется.
Эт0 объясняет структуру таких функций:
// Префиксная форма оператора инкремента
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, сУЩествляется следующим образом:
Шаг 1. Если и и V имеют различные знаки, то
—если и>0, то возвращается u+| v|,иначе возвращается -(v+|u|),
——отметим, что в обоих случаях результат получен с использованием операции сложения,
иначе, получая цифру за цифрой от каждого операнда, вычитаем одну из другой, используя обычное арифметическое вычитание.
Шаг 2. Определяется знак, и возвращается результат операции.
Отметим, что операторы сложения и вычитания вызывают друг друга в процессе вычисления результата.
Предположим, что мы вычисляем u*v, где u, v — представители класса Verylong. Умножение производится применением обычного метода:
Каждую цифру V мы умножаемся на и с помощью внутренней функции multdigit (). Суммируя эти произведения, получаем произведение и и v.
Для оператора целого деления / используется алгоритм обычного деления столбиком. Сначала мы удостоверимся, что делитель 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 ).
Операторы преобразования типов
>【ке>【ке-long-long6」’ C++ число с плавающей точкой могло быть присвоено целому числу В 幺зь【к° т ЦрИ ЭТом преобразование типов происходит неявно. Для класса ° нао"°Р желательно иметь такие же свойства. Имеются два вида преобразо-
>【ке
>【ке
-long
-long
вания-
образование чисел Verylong во встроенные типы данных. Этот вид пре- 1 1 tf азования соответствует операторам преобразования, предусмотренным
C++. В классе используются три оператора преобразования:
.Оператор int() преобразует число Verylong во встроенный целочисленный тип, если оно находится в границах диапазона, иначе выдается сообщение об ошибке.
.Оператор double О преобразует число Verylong в число с плавающей точкой двойной точности, когда это применимо.
Оператор char() преобразует Verylong число в указатель на символьный тип. Это преобразование полезно при использовании подпрограмм из библиотеки <string.h> (см. гл. 11).
2. Преобразование данных встроенных типов в числа Verylong. Этот вид преобразования выполняется конструкторами класса. Конструкторы читают тип данных параметров и осуществляют соответствующее преобразование, которое преобразует встроенный тип данных к числу Verylong.
Закрытые методы
Некоторые операции над полями данных не должны быть видны пользователю. Методы, реализующие их, как и сами поля данных, объявляются как private. Объекты класса Verylong имеют 3 закрытых метода. Их содержание описывается следующим образом:
* multdigit (int num) умножает Verylong число на num, где num — целое число, лежащее между 0 и 9. Этот метод вызывается при умножении двух Verylong чисел.
muLtlO(int num) умножает Verylong число на 10 в степени num. Например v-multl0(5) эквивалентно v*100000.
strrev(char *s) полностью меняет порядок символов строки s на обрат- 11M近 и возвращает это в качестве результата.
6」7 ДруГие функции
йи?Сс ДОл^ен иметь набор доступных методов (функций), входящих во внеш- есл Т'еРбе直& Класс Verylong не мог бы рассматриваться как законченный, 1 отсутствовали следующие функции:
Для данного положительного целого числа “ целочисле ный корень а однозначно определяется соотношениями“"ь證
а? $ 6 < (а + I)?.%、
Например, целочисленный квадратный корень из 105 естк ш 105 < П2. '咏融明
Имеется много способов поиска целочисленного квадрат " ложительного целого числа. Метод, который мы используем Ко^ Пг тождестве (а + 6)2 三 а? + 2ab 4- Ь2. Алгоритм следующей: ' ℃“(^曲 j
Шаг 1.
Начиная от младшей цифры числа разобьем его на
по 2 цифры в каждом. Число сегментов равно количест6ГМе11Ть: целочисленного квадратного корня."У@$р
Шаг 2.
Получим первую цифру, вычислив целочисленный квад корень из первого сегмента. Запишем результат в д ‘迎由
Шаг 3.
Шаг 4.
Вычтем а2 из первого сегмента и получим остаток.
Делим остаточный член на 2а, чтобы получить вторую пиа корня Ь. Запишем результат.屮Р
Шаг 5.
Шаг 6.
Вычтем 2ab и 6? из остатка в соответствующей позиции.
Если все требуемые цифры получены, возвращаем конечный результат.
Шаг 7.
Переходим на шаг 4 для нахождения следующих оставшихся цифр целочисленного квадратного корня.
Для иллюстрации рассмотрим следующий пример: sqrt (394384) = ?
3 9 4 3 8 4 -36 атбПервая цифра
3 4 3 34 + 2*6т2Вторая цифра
—2 4 2&*b=2*6*2 = 24
—4б? = 4
9~ 9 8 4 998 + 2 * 62 т 8Третья цифра
-9 9 2 2a“с= 2*62*8 = 992
—6 4 d = 64
й
Поэтому, sqrt (394384) = 628.
pow (const Verylongfe, const Verylongfe)抨
Предположим, что мы хотим вычислить ж29. Мы могли пр°ст0 т0: с £ и умножать на х двадцать восемь раз. Однако возможно полу «^ в же самый ответ, проведя лишь семь умножений: берем х, возно^^1М $ квадрат, умножаем на х, возводим в квадрат, умножаем на х, в 祖。** квадрат, снова возводим в квадрат, умножаем на х. Символьно эт° изобразить последовательностью
⑴一> * t d T / t ⑴7 t /4 T /8 T £29
ьность могла быть получена из двоичного представления ^едон^тель 力 обратном порядке: 11101. Заменим каждую《]» 3口 : 29, 3aMe^^ каждый《0» — S; получаем SX SX SX S SX и удалим ин*; символ。"冰 ^ние sx , получаем последовательность SXSXSSX, ко- па^ руюшее bX\z операций:《S》интерпретируется как возведение в %ая задает п^^к умножение на ж. Этот метод легко может быть запро- ^здрат " «Х£兄 удобства считаем, что число уже записано справа налево :рамммрован為。工 с1арШих разрядов к младшим).
(т 黑喘肾冲п ПОЛОЖИТеЛЬНОГО N:
Щаг 1-Щаг 2.Щаг 1-Щаг 2.Щаг 3.Щаг 3.Усыновим几,У — 】,%"”
Щаг 1-
Щаг 2.
Щаг 1-
Щаг 2.
Щаг 3.
Щаг 3.
Если N нечетно, У — F*Z, N — [N/2], если N = 0, возвращаем ответ У, иначе N ~[N/2]
С 1 обозначает целую часть х. Она определяется как наибольшее целое JSчем х. Рассмотрим этот алгоритм на примере вычисле-С 1 обозначает целую часть х. Она определяется как наибольшее целое JSчем х. Рассмотрим этот алгоритм на примере вычисле-НИЯ £29:NУZПосле шага 1.291XПосле шага 3.14ж力2X4После шага 3.7XПосле шага 3.3力6力8После шага 3.1х13力16После шага 3.0£29—• div(const Verylongfe,constVerylong&)НИЯ £29:NУZПосле шага 1.291XПосле шага 3.14ж力2X4После шага 3.7XПосле шага 3.3力6力8После шага 3.1х13力16После шага 3.0£29—• div(const Verylongfe,constVerylong&)значение, меньшеезначение, меньшееZ ~ Z * Z и идем на шаг 2
С 1 обозначает целую часть х. Она определяется как наибольшее целое JSчем х. Рассмотрим этот алгоритм на примере вычисле-
С 1 обозначает целую часть х. Она определяется как наибольшее целое JSчем х. Рассмотрим этот алгоритм на примере вычисле-
НИЯ £29:
N
У
Z
После шага 1.
29
1
X
После шага 3.
14
ж
力2
X4
После шага 3.
7
X
После шага 3.
3
力6
力8
После шага 3.
1
х13
力16
После шага 3.
0
£29
—
• div(const Verylongfe,
const
Verylong&)
НИЯ £29:
N
У
Z
После шага 1.
29
1
X
После шага 3.
14
ж
力2
X4
После шага 3.
7
X
После шага 3.
3
力6
力8
После шага 3.
1
х13
力16
После шага 3.
0
£29
—
• div(const Verylongfe,
const
Verylong&)
значение, меньшее
значение, меньшее
Обычный оператор деления / осуществляет целочисленное деление. Во многих случаях, однако, мы нуждаемся в вещественном значении частного
от деления Verylong чисел. Функция div(u,v) используется, чтобы осуще- ^витъ деление с плавающей запятой, и возвращает результат типа double, счисление функции происходит следующим образом:
начала мы убеждаемся, что знаменатель не равен 0.
3翼м м“ находим порядок частного.
лай м“ находим мантиссу частного цифру за цифрой обычным де- 貶ем столбиком.
значащ^ Зак<^чено, есл” не имеется никакого остатка, или получено 16
‘L8. Потоки
РакРак°РН°РН0пеРаторЬ1» н
Рак
Рак
°РН
°РН
работают Исп°льзуются для ввода и вывода, соответственно. Эти опе- Нен;РегрУ2ем о' всеми встроенными типами данных. Для класса Verylong ^^^ Нз входНог^Пе^атоРьг ввода/вывода. В следующем примере читается зна- "・"°тока в переменную х типа Verylong и затем оно отобра-
#include <iostream.h>
#include H\verb|Verylongl.h"
void main()
Verylong x;
cin >> x;
cout << MThe value entered is " << x << endl;
Реализация этих функций достаточно тривиальна:
istreamft operator » (istreamfe, Verylongfe) Оператор читает последа вательно символы цифр из потока (например, с клавиатуры), принимая максимально до 1000 символов.
ostream& operator << (ostreamfe, const Verylongfe) Оператор отображает значение Verylong числа на дисплее.
6.2. Класс рациональных чисел
Введение
В математике числовая система строится путем расширения классов чисел. В этом разделе мы построим класс чисел Rational, который является естественным расширением целых чисел Z. Математика рациональных чисел была описана в разд. 2.3.
Здесь мы сталкиваемся снова с проблемой, упомянутой в предыдущем разделе, а именно встроенные типы данных в C++ ограничены в диапазоне. Чтобы решить эту проблему, для представления числителя и знаменателя Rational числа вместо int используется Verylong. Однако в расширении мы идем дальше; для построения класса мы используем возможности шаблона класса, имеющегося в C++. Это позволяет пользователям выбирать тип данных, который удовлетворит их потребности.
Для начала мы должны определить поведение чисел АТД Rational:
Это — шаблонный класс, для которого тип числителя и знаменателя должен быть определен пользователями.
Создалие нового экземпляра Rational числа должно быть просто.
Rational числа можно приводить к более простой форме, используя iviro- ритм наибольшего общего делителя.
Доступны такие арифметические операторы, как +, -, *, /.
Доступны операторы присваивания и присваивания с изменением =, +=,
=,*=, /=•
Доступны операторы сравнения >, >=, <, <=, ==, !=.
Поддерживается приведение к типу double.
Доступны функции, которые возвращают числитель, знаменатель и др°^ ную часть Rational чисел.
Поддерживаются операторы ввода и вывода Rational чисел.
return os;
« •'nil";
№七皿n os;
templatetemplatetemplatetemplate<class T> Type<T>::Type() : thing(T(0)) {}
template
template
template
template
<class T> Type<T>::Type(T t) : thing(t) {}
template <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>::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 { os « thing; }
template <class T>
^stream& operator << (ostreamfe os, const Type<T> *t)
t'>print(os);
} return, os;
Jr * cons (const Element *el, const Element *e2)
Pair *p = new Pair;
P'>car(el);
2 Шаблон класса
” онаЛьные числа с числителем и знаменателс'м типа int нс могут прс^ста- F"”" все множество рациональных чисел из-за ограничении на данные типа в^'Т^то возможно только, если числитель н знаменатель можно представлять и во всем диапазоне целых чисел. Класс Verylong, сконструированный в 口融 тпущем разделе, в принципе позволяет представлять ц(\чые числа произ- "Р ой длины. Поэтому необходимо так или иначе включить Verylong числа
в Ration" класс.
° ц]а6nон в C++ обеспечивает параметризованные типы. В соответствии с
эТОй особенностьюэТОй особенностьюодин и тот же код может использоваться в отношении р;〔з-
эТОй особенностью
эТОй особенностью
лиЧных типов, где типлиЧных типов, где тип—параметр тела кода. Шаблоны классов дают простой
лиЧных типов, где тип
лиЧных типов, где тип
способ повторно использовать код, что позволяет транслятору автомлтизиро-
вать процесс реализации типов.
Мн реализовали Rational числа как шаблонный класс. Причина в том, чтобы элементы данных, числитель и знаменатель, могли 6川 иметь тип int или Verylong по желанию пользователя. Имея эту особенность, можно спокойно осуществлять любые вычисления, не боясь того, что числа могут выйти за
границы диапазона.
Поля данных
В классе Rational объявляет два поля данных тина Т. Это означает, 'по пользователь должен определить фактический тип данных, предоставляс'мый дли использования в классе. Первое поле1 р содержит числитель, второе ноле q знаменатель Rational числа. Заметьте, что в соответствии с концепцией с-кры- тил данных оба поля объявлены как private (закрытые). Поэтому они доступны только в пределах класса.
Конструкторы
Объявить Rational число можно ст ('дующим и способами:
〃 Объявление Rational числа и int-типа, 〃 инициализированного значением 0 Rational <int> u;
〃 Объявление v, инициализированного значением 5 Rational<int> v(5);
(/ Объявление w, инициализированного значением 2/3 ^tigakint〉w(2,3);
,Объявление x Verylong-типа, инициализированного значением
/ 127762/2384623
ational<Verylong> x(127762,2384623);
力 Объявление у, инициализированного значением
r 'а Веделами int-диапазона
lonal;Verylong> у("32872134727762","348972938479822384623");
В классе имеются три конструктора, которые дают различные способы кструирования Rational числа:"、
1.2.3.1.2.3.Конструктор по умолчанию объявляет переменную типа Rational диализирует ее нулем.Конструктор по умолчанию объявляет переменную типа Rational диализирует ее нулем.Rational (const Т N) объявляет переменную типа Rational и зирует ее значением N.Rational (const Т N) объявляет переменную типа Rational и зирует ее значением N.и Инц,и Инц,нннцналтнннцналтRational (const Т N, const Т D) объявляет переменную типа R-atio^ и инициализирует ее значением N/D.Rational (const Т N, const Т D) объявляет переменную типа R-atio^ и инициализирует ее значением N/D.
1.
2.
3.
1.
2.
3.
Конструктор по умолчанию объявляет переменную типа Rational диализирует ее нулем.
Конструктор по умолчанию объявляет переменную типа Rational диализирует ее нулем.
Rational (const Т N) объявляет переменную типа Rational и зирует ее значением N.
Rational (const Т N) объявляет переменную типа Rational и зирует ее значением N.
и Инц,
и Инц,
нннцналт
нннцналт
Rational (const Т N, const Т D) объявляет переменную типа R-atio^ и инициализирует ее значением N/D.
Rational (const Т N, const Т D) объявляет переменную типа R-atio^ и инициализирует ее значением N/D.
Можно было бы объединить эти три конструктора в один (к примеру, 口昨 вый конструктор выглядел бы как Rational (const Т = 0, const Т = Однако компиляторы, которые мы используем, не принимают эту конструк: цию, потому что это — шаблон класса. Чтобы не создавать себе проблем, м* включаем в описание класса все три конструктора.
В процессе конструирования вызывается внутренний метод gcd() для приведения Rational числа в его стандартную форму. Конструктор копирования и деструктор, предоставляемые компилятором, работают хорошо в нашем классе, но для полноты картины мы включили их в класс.
Операторы
Так как класс Rational — математический объект, общие операторы, используемые в классе — арифметические операции. Мы включили много операторов в класс, а именно
-(унарный), +, -, *, /, =, +=, -=, *=, /=, ==, ! = , >, >=, <, <=•
Определения некоторых из этих операторов следующие:
а
с
а * d + 6 * с
а
с
а * d — b * с
Ь
+ d -
Ь
d」
b * d
а
а * с
а
с
* ——
b
d
d
匕* с'
? == - => (а == с) и (b == d),^ < ~ => (а * с?) < (6 * с),
о ао а
Реализация этих операторов достаточно проста и мы не будем останавливаться на ней.
Операторы преобразования типов
Оператор преобразования типов позволяет при необходимости перевести т"? данных в другой. Представление Rational числа в виде числа с плавакинс" запятой всегда полезно. Мы включили оператор преобразования в тип данный double. Операторы преобразования в классе существует в двух видах. Операторы первого вида позволяют привести данные любых типов в тип double
атОрЬ1 второго вида переводят данные лишь Rational<Verylong>~ и тип double. Можно спросить, для чего нам нужен дополнительный опе- 冲$ Впреобразования для Rational<Verylong>? Как мы увидим в следующем РЗ^^ре там имеется лучший и более точный метод для Rat ional <Verylong> S'莪щмй метод, который работает для любого типа данных, просто возвра- 皿磔,приведенное к double значение Rational числа:
return double(p)/double(q);
协s метод тем слУ113^协s метод тем слУ113^работает достаточно хорошо для Rational<Verylong> чисел в об- но он подводит иногда из-за ограничений точности в double. На-работает достаточно хорошо для Rational<Verylong> чисел в об- но он подводит иногда из-за ограничений точности в double. На-ПОМНИМ,ПОМНИМ,чточтоимеется функция, называемая div(), которая производит делениеимеется функция, называемая div(), которая производит делениес ДВОЙНОЙс ДВОЙНОЙточностью Verylong чисел. С этой функцией мы способны ту же ра-точностью Verylong чисел. С этой функцией мы способны ту же ра-g^yy делять лучше.g^yy делять лучше.
协s метод тем слУ113^
协s метод тем слУ113^
работает достаточно хорошо для Rational<Verylong> чисел в об- но он подводит иногда из-за ограничений точности в double. На-
работает достаточно хорошо для Rational<Verylong> чисел в об- но он подводит иногда из-за ограничений точности в double. На-
ПОМНИМ,
ПОМНИМ,
что
что
имеется функция, называемая div(), которая производит деление
имеется функция, называемая div(), которая производит деление
с ДВОЙНОЙ
с ДВОЙНОЙ
точностью Verylong чисел. С этой функцией мы способны ту же ра-
точностью Verylong чисел. С этой функцией мы способны ту же ра-
g^yy делять лучше.
g^yy делять лучше.
return div(p,q);
В целях иллюстрации рассмотрим следующий пример: // division.ерр
#include <iostrecim.h>
#include "\verb|VerylongI.hn
void main()
{
Verylong P("999") , QC'lll") , D(”105”);
P = pow(P,D)P = pow(P,D)// 999F05 = 9.0027 7e+314// 999F05 = 9.0027 7e+314(превьппена граница(превьппена граница////диапазонадиапазонатипа double)типа double)
P = pow(P,D)
P = pow(P,D)
// 999F05 = 9.0027 7e+314
// 999F05 = 9.0027 7e+314
(превьппена граница
(превьппена граница
//
//
диапазона
диапазона
типа double)
типа double)
}}1.56842e+100 - OK6.37583e-101 - OKNaNNaN1.56842e+100 - OK6.37583e-101 - OKNaNNaNQ = 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; //
}
}
1.56842e+100 - OK
6.37583e-101 - OK
NaN
NaN
1.56842e+100 - OK
6.37583e-101 - OK
NaN
NaN
P или Q превышают rpa-P или Q превышают rpa-Оператор double (P) /double (Q) не работает, когда
P или Q превышают rpa-
P или Q превышают rpa-
皿恥 диапазона типа данных double, которые составляют приблизительно ^1-7977 х 10308, как сказано выше. Слово NaN есть сокращение для «не число». 侬 этого примера мы заключаем, что определение оператора преобразования второго вида для Rational<Verylong> необходимо.
6・2・7. Закрытые методы
^^крЫтые методы — это внутренние функции, которые известны только вну- ?" класса. Они недоступны вне класса. Имеется лишь один метод в нашем с ^се. Функция gcd(T а,Т Ь) возвращает наибольший общий делитель а и Ъ Испольдованием следующего алгоритма:
Шаг 1.Шаг 1.Шаг 2.Шаг 2.До тех пор, пока & > 0, делается следующее:
Шаг 1.
Шаг 1.
Шаг 2.
Шаг 2.
*т — a mod &
*а —, и b — т.
В качестве результата выдается а.
Например, gcd(4,8) возвращает 4.
Другие методы
В классе определены только три метода, отличные от арифметических Оп^ раторов: num() , den() и frac(). Они возвращают числитель, знаменатель н дробную часть Rational числа, соответственно.
template <class Т> Т Rational<T>::num() const { ... }
template <class Т> Т Rational<T>::den() const { ... }
template <class T> Rational<T> Rational<T>::frac() const { ... }
Отметим, что они объявлены как функции-консталты. Это указывает, что их значения не могут быть изменены. Любое значение в C++ может быть объявлено как константа. Константная переменная привязана к значению, которое никогда не может быть изменено. Поэтому только константные операции могут быть выполнены на значении. Приложение этих методов дается в следующей программе:
// testfrac.срр
#include <iostream.h>
#<include иRational.h'1
int main()
Rational<int> r(7,3); int n = r.num(): int d = r.den();
Rational<int> f; f = r.fracO ;
cout << "и cout << "d cout << "fcout << "и cout << "d cout << "f« n « d « f« n « d « fendl; // => 7
cout << "и cout << "d cout << "f
cout << "и cout << "d cout << "f
« n « d « f
« n « d « f
endl; // => 3
endl; // => 1/3
}}return 0;return 0;6・2・9・6・2・9・ПотокиПотоки
}
}
return 0;
return 0;
6・2・9・
6・2・9・
Потоки
Потоки
В этом разделе мы описываем,В этом разделе мы описываем,как операторы >> и << перегружены, чтобы
В этом разделе мы описываем,
В этом разделе мы описываем,
осуществить ввод и вывод для Rational чисел:
Оператор ввода потока » осуществляется таким образом, чтобы можн° было вводить дробь подобно а/b с клавиатуры, и класс мог бы распознать-
△ агть числитель и b — знаменатель. С другой стороны, если введена дробь, класс должен распознать, что это — целое число (то есть рацион ьное число со знаменателем, равным единице).
н Чтобы удовлетворить эти потребности, используются некоторые функ- й3 библиотеки <iostream.h>. Процедура ws очищает все незаполненное ^^^ранство после ввода. Функция реек() используется для предваритель- 閣го просмотра следующего символа, и функция get () читает текущий символ из входного потока. Никаких предосторожностей против ошибок ввода не принято. Пользователи несут полную ответственность за корректность использования функции.
Выполнение оператора вывода потока « более просто. Если знаменатель Rational числа равен единице, то выводится только числитель. Иначе выводится значение дроби.
6・3・ Класс комплексных чисел
в.3.1. Введение
В библиотеке C++ доступен заголовочный файл <complex. h>. Однако тип данных вещественной и мнимой части этих чисел — double. Этого не достаточно для прикладных программ, которые требуют точных манипуляций с комплексными числами. Это ограничение потребовало создать другой класс комплексных чисел с использованием шаблона. Этот класс позволяет нам использовать класс Rational чисел, разработанный в предыдущем разделе, в качестве вещественной и мнимой частей комплексного числа. Таким образом, точное оперирование с комплексными числами становится возможным. Мы опишем представление и дадим реализацию для класса Complex чисел. Пример в конце *того раздела демонстрирует, как этот класс может быть использован.
Так как Complex АТД ——числовой тип, его объекты должны иметь все свой- льа, которыми обладают числа. Поведение чисел АТД Complex следующее:
Это — шаблонный класс, для которого тип вещественной и мнимой частей должен быть определен пользователем.
Имеются три простых способа создать Complex число.
Доступны арифметические операторы типа +, -, *, /.
Доступны операторы присваивания и присваивания с изменением =, +=, ■=> *=, /=.
Доступны шесть операторов сравнения >, >=, <, <=, ==, !=.
е Реализованы следующие шесть методов:
L realPart () возвращает вещественную часть Complex числа.
2. imagPart () возвращает мнимую часть Complex числа.
3・ magnitude() возвращает модуль Complex числа.
4. argument () возвращает угловой параметр Complex числа в полярных координатах.
5・ conjugate() возвращает число, сопряженное с Complex числом.
6・ negate () возвращает инверсию Complex числа.
• Под^^ержи ваются операции ввода и вывода с Complex числами.
Шаблон класса
Как и в классе Rational чисел, пользователи могут зах()тОть контроль над компонентами Complex числа. Это может 6i>『q "”"卜 15 тем реализации класса как шаблонного. Вот некоторые возм ^0СТИ1'нУг(^^ задания класса Complex чисел:Нь【©(:^^-
Complex<int> z = х + iy х,у имеют тип данных int
Complex<double>
2 =① + £g 工〉у имеют тип данных 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
Заметим, что пробел между двумя символами >, которыми оканчиваютс на шаблонов, требуется всегда. Если он опущен, транслятор по этому п будет выдавать сообщение об ошибке.”'
Поля данных
Так как имеются два различных способа представления комплексного числа, мы должны выбрать одно из них для внутреннего представления полей данных. Мы будем использовать декартову форму комплексного числа как более простую. Однако выбор всегда остается за читателем. Независимо 0T того, какое представление было выбрано, пользователи не должны задумываться「 нем. Комплексные числа в декартовой форме имеют два компонента: вещественную и мнимую части. В классе они представлены нерешенными real и imag.
Конструкторы
Наиболее важные функции класса — конструкторы. Мы предоставляем трь различных способа создания Complex чисел:
1.1.2.2.3.3.Заданный по умолчанию конструктор объявляет Complex перемени} ^^^( специфицирования его начального значения. Оно устанавлива('тся . автоматически.
1.
1.
2.
2.
3.
3.
Конструктор, который содержит один параметр, объявляет Сошр1еХ 油. с мнимой частью, равной нулю, и вещественной частью, “нни”汇【"
Complex 喘cneuuM"叩Complex 喘cneuuM"叩ной значением, специфицированным в параметре. Конструктор, который содержит два параметра, объявляет с вещественной и мнимой частями, инициализированвь^^11 ванными значениями параметров.
Complex 喘cneuuM"叩
Complex 喘cneuuM"叩
Следующие примеры поясняют, как Complex число может быть ‘° личными способами:
числа
значением 0
числа V double
me Comply
и int-типа;
// U暮int> u, comp11
яие Complex
^爲k。
"叩„ й инициализировать его
空:需LA
Complex
„ й инициализировать его Объявить X :
3…(2,3),
значением 0
типа;
значением 2
значением 2 + 3i
и И инициализировать у
〃 Объявить и инициализировать z
,С компонентами Rational<Verylong>-nina
•'omplex<Rational<Verylong> > z (Rat ional<Verylong> (" 12345" , "345123"), “ LtionaKVerylong>("987659876543", "82567834536572693487");
Как и в случае класса Rational, можно было бы попытаться заменить одним конструктором с заданным по умолчанию параметром
Complex (const Т = 0, const Т = 0)
eq три конструктора, ^'помянутые выше. Однако из соображений общности ”и сохраняем их по отдельности.
6.3.5.Здесь6.3.5.ЗдесьОператорыОператорыКонструктор копирования и де(:тру ктор тривиальны в этом классе. Однако законченности мы включили их в описание класса.
6.3.5.
Здесь
6.3.5.
Здесь
Операторы
Операторы
ввклагсе Q1。'"""” реализацию арифметических операторов, имеющихся ператоры, которые были rK^jerpynceiibi, включают
в
в
Парный) +一
3切坨зеле,,水,/,匸,+二,-=,*=, /=, <, <=, >, >=, ==, ! = ■
” для этих операторов сл('Д}гЮ1цие:
ъ)両-)+ + + a a aъ)両-)+ + + a a a+ (с + “力=(а + с) + г (6 + d) 一化+ ,沁)=卜上-r) + "Ь一 d) X 卜:+ id) = (ac — bd) + z'(ad + be)
ъ)両-)
+ + + a a a
ъ)両-)
+ + + a a a
(a + 力)ас + db . (be — ad\- с2 + d2 + 2 (с2 +壮丿
J +,b) < « + “/) > („2 + 俨)< 化2 + d2)
“+£ь)==+?“)今«, == C) н (% == (/)
Реализация этих операторов непосредственно вытекает из определения татели не должны иметь никаких трудностей в их понимании.1 '乐
Операторы преобразования типов
Мы имеем
Z С Q С R С С,
т. е. множество целых чисел есть подмножество рациональных чисел, кото являются по дм ножество м вещественных чисел, являющихся подмножеств ' комплексных чисел. Таким образом, набор комплексных чисел — надмНо^М ство всех других множеств, включая множества, задаваемые встроенным^ 丁口 пами данных. Поэтому мы не определяем никаких функций преобразования типов к любому встроенному типу данных, потому что при таком прообраза вании комплексные числа теряли бы часть информации.
Другие функции
Далее мы опишем функции, в которых реализованы некоторые свойства Complex чисел а + »Ь, необходимые для нас:
Вещественная и мнимая части комплексного числа.
Модуль комплексного числа, который определяется как,出 + 炉
Аргумент числа (в полярных координатах), который определяется как arctg(b/a). Формулы в этом и предыдущем пункте задают правила перевода комплексного числа из декартовой формы в полярные координаты.
Комплексно сопряженное с а + гб, которое равно а — ib.
Инверсия комплексного числа а + 池 которое равно —а — ib.
Они легко могут быть получены с помощью шести методов, определенных в классе:
realPartO , imagPart(), magnitude() , argument () , conjugate() , negate0 •
Следующая программа демонстрирует использование этих методе#:
// scomplex.ерр
#include <iostream.h>
#include "MComplex .h11 int main()
Complex<int> c(3,4); // описывается комплексное число 3+4i
cout << c.realPcLrt() << endl; // 3
cout << c. imagPcLrt () << endl; //4
cout << c.magnitude() << endl; // 5
cout << c. cLTgument () << endl; // 0.927295
ut « c.conjugateO « endl; // (3,-4i) ^;ut << c.negateO << endl; // (-3,-4i)
return 0;
} Иля завершенности класса Complex нам еще необходимо включить все спе-
Chzp математические функции, которые могут быть применены к ком- ^^сным числам, например
sin(), cos() , exp () , cosh(), sinh (), sqrt(), log().
реализацию этих функций оставляем в качестве упражнений для читателей.
6.4. Класс кватернионов
Введение
Кватернионы, описанные в разд. 2.7, — математическая структура самого верхнего уровня по отношению к основным числовым структурам, таким, как рациональные и комплексные числа. Поэтому кватернионы базируются на основных структурах, введенных нами ранее. Описание АТД Quaternion может быть суммировано следующим образом:
Это 一 шаблонный класс, для которого тип коэффициентов при компонентах чисел должен быть определен.
Создание экземпляров чисел Quaternion просто.
Арифметические операции для Quaternion перегружаемы. Например, "(унарный), +, -, *, /.
Доступен оператор присваивания =.
Доступны операторы, такие, как нахождение модуля, сопряжения и инверсии Quaternion чисел.
Поддерживаются операторы потокового ввода и вывода.
6・4・2・ Шаблон класса
КоэффццИенть1 Quaternion могут иметь тип int, double или Verylong и т. д.
Добные классы лучше реализовывать в форме шаблона. В этом случае мы
Н°6rаем дублирования кода для различных типов данных коэффициентов.
冰е мы перечислим некоторые возможные способы объявления чисел класса 8agmion:
Quaternion<int>, Quaternion<double> объявляют Quaternion число с ко- .эФфициентами встроенного типа int и double, соответственно.
Q^aternion<Verylong> объявляет Quaternion число с коэффициентами
.Пользовательского типа Verylong.
"U&t±Fion<Rational<iiit> >, Quaternion<Rational<Verylong> > объяв-
"ак)т Quaternion число с коэффициентами Rational-типа.
Quaternion<Complex<int> >, Quaternion<Complex<Verylong> >, Quaternion<Complex<Rational<int> > >, Quaternion<Complex<Rational<Verylong> > > — различные способы к явить Quaternion числа с коэффициентами типа Complex.%、
Обратите внимание на успешное соединение четырех пользовательских ти для формирования нового типа данных.'
Поля данных
Чтобы определить кватернион однозначно, мы должны задать коэффициенты при четырех компонентах. Это в точности содержимое полей данных класса Quaternion. Поля г, i, j , к представляют коэффициенты при 1,/, J, к с» ответственно.
Конструкторы
Создать Quaternion число просто. Достаточно пользователю обеспечить эти четыре коэффициента, и Quaternion число создано. Пользователь может вообще не заботиться о значениях коэффициентов: посредством вызова заданного по умолчанию конструктора коэффициенты инициализируются нулем. Ниже —некоторые примеры того, как экземпляры Quaternion чисел могут быть созданы:
// Объявить и int-типа и инициализировать его значением О Quaternion<int> и;
// Объявить 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>(3,4),Rational<int>(-4,5));
Конструктор копирования и деструктор тривиальны.
6.4*5. Операторы
Предположим, что q и р —два произвольных кватерниона, q + P, q-p, q*P) q/p
перегружаются как сложение, вычитание, умножение, деление дир соответ” ственно; принимаем во внимание, что ~q есть q со знаком минус. Математ"%” ские детали были описаны в разд. 2.7.
Другие методы
В этом разделе мы хотели бы продемонстрировать использование метод06 conjugate (), inverseO , magnitude О и оператора нормализации (~):
〃 sqUater.cpp
• elude <iostream.h> ^clude "Quatern.h"
gid main()'Quaternion<double>gid main()'Quaternion<double>Ql(3,4,5,6),Q2 = QI .conjugateO , Q3 = QI. inverseO ;Ql(3,4,5,6),Q2 = QI .conjugateO , Q3 = QI. inverseO ;double Mag = QI.magnitude(), Magz = ("QI).magnitudeO;double Mag = QI.magnitude(), Magz = ("QI).magnitudeO;
gid main()
'Quaternion<double>
gid main()
'Quaternion<double>
Ql(3,4,5,6),
Q2 = QI .conjugateO , Q3 = QI. inverseO ;
Ql(3,4,5,6),
Q2 = QI .conjugateO , Q3 = QI. inverseO ;
double Mag = QI.magnitude(), Magz = ("QI).magnitudeO;
double Mag = QI.magnitude(), Magz = ("QI).magnitudeO;
cout « "QI = " << QI << endl;
cout << "Q2 = сопряженное к QI = " << Q2 << endl;
c0Ut << "Q3 = инверсия QI = “ << Q3 << endl;
cout << "Mag = модуль QI 二"<< Mag << endl;
cout << "Magz = модуль нормализованного QI = " << Magz << endl;
cout << endl;
cout <<"QI * Q2= " << QI * Q2<< endl;
cout <<"Q2 * QI= " << Q2 * QI<< endl;
cout <<"Mag"2 =квадрат модуля= " << Mag* Mag << endl;
cout << endl;
cout << "QI * Q3 = " << QI * Q3 << endl; cout << "Q3 * QI = " << Q3 * QI << endl;
}
Результаты
QI = (3,4,5,6)
Q2 = сопряженное к QI = (3,-4,-5,-6)
Q3 = инверсия QI = (0.0348837,-0.0465116,-0.0581395,-0.0697674)
Mag = модуль QI = 9.27362
"agz = модуль нормализованного QI = 1
QI * Q2 = (86,0,0,0)
¥ * QI = (86,0,0,0)
戋 2 = квадрат модуля - 86
%; * Q3 = (i.o.o.o)
* Qi = (1,0,o,o)
“7 Потоки“7 Потоки①Ункцин①Ункцин^тает^таетпотокового ввода и вывода в классе прямые. Функция ввода простопотокового ввода и вывода в классе прямые. Функция ввода просто^Рнй(^Рнй([ОН[ОНчетыре коэффициента каждого элемента ввода. В функции вывода ква-четыре коэффициента каждого элемента ввода. В функции вывода ква-:q = ai*l + a/*/ + aj*J + QK*K форматируется и экспортируется ahaj,ai<).:q = ai*l + a/*/ + aj*J + QK*K форматируется и экспортируется ahaj,ai<).
“7 Потоки
“7 Потоки
①Ункцин
①Ункцин
^тает
^тает
потокового ввода и вывода в классе прямые. Функция ввода просто
потокового ввода и вывода в классе прямые. Функция ввода просто
^Рнй(
^Рнй(
[ОН
[ОН
четыре коэффициента каждого элемента ввода. В функции вывода ква-
четыре коэффициента каждого элемента ввода. В функции вывода ква-
:q = ai*l + a/*/ + aj*J + QK*K форматируется и экспортируется ahaj,ai<).
:q = ai*l + a/*/ + aj*J + QK*K форматируется и экспортируется ahaj,ai<).
6・5・ Класс производных'
Введение
До сих пор мы имели дело с числовыми типами; здесь мы собираемся совершенно другую абстракцию. Класс Derive обеспечит нас оператором氏 торый применяется к числовому типу и результат операции — снова чи^ К(к тип. Класс Derive характеризуется следующим:
Тип данных результата — шаблон Т, определяемый пользователем.
Конструкция выражений проста: используются арифметические операт типа - (унарный), +,*, /.叩*
Метод set() используется, чтобы определить точку (число), где бер^ производная.'
Значение производной может быть получено, используя метод df ().
Поддерживается оператор вывода производной.
Поля данных
Имеются только два поля данных в классе: одно является переменной и, в которой хранится значение точки, где производная вычисляется; другое — переменная du хранит значение производной в и.
Конструкторы
В классе имеются следующие конструкторы:
Заданный по умолчанию конструктор объявляет независимую Derive переменную. Зависимая переменная объявляется с использованием оператора присваивания или конструктора копирования.
Derive (const Т num) объявляет постоянное число num.
Закрытый конструктор Derive (const Т, const Т) используется, чтобы определить значения точки и производной в ней.
Конструктор копирования и оператор присваивания в классе триви;шьны- и осуществляют не более чем поэлементное копирование.
Деструктор тривиален.
Например, объявление у = 2/ + 1 требует следующих инструкций: Derive<int> х;// Это объявление независимой переменной х
Derive<int> у = 2*х+1; // ... и зависимой переменной у.
Операторы
Арифметические операторы
-(унарный), +, -, *, /
перегружены, чтобы осуществлять операции, удовлетворяющие условиям вил дифференцирования, как описано в разд. 2.9.
3Mm^ методы доступны в классе:
д set (const Т) оперирует с независимой переменной. Она использует* М чтобы специфицировать точку и, в которой вычисляется производная /. Метод df (const Derive 也х) возвращает значение производной в х.
• исковый оператор вывода << выдает значение и.
мотрим функцию /(£)= 2/3+5t + L Предположим, что мы хотим оценить рачение 切(£ = 2)/dc. Выполним следующую программу:
〃 sderivel.cxx
#include <iostream.h>
♦include "Derive.h"
void main()
Derive<int> x;
x.set(2);
Derive<int> у = 2*x*x*x + 5*x + 1;
cout << "Производная у в x = " << x << " равна << df(у)<< endl;
Результат
Производная у в x = 2 равна 29
Рассмотрим другую функцию g(£)= t? + 3/х. Предположим, что мы хотим оценить значение dg{x = 37/29)/dw, тогда выполним программу
〃 sderive2.cxx
#include <iostream.h>
Mncluda "Derive. h"
^include "Rational.h"
fid main()
Oerive<Rational<int> > x;
x-set(Rational<int>(37,29));
Deriv6<Rational<int> > c(3);
Derive<Rational<int> > у = x*x + с/х;
cout << " Производная у в x = " << x << " есть "
1« df(у) << endl;
Результат
Производная у в х = 37/29 есть 28139/39701
Обратите внимание, что после объявления независимой переменной всегда устанавливаем значение, где производная будет браться. Это ва* 迎川 должно быть сделано до объявления зависимой переменной у. Иначе м " быть получены непредсказуемые результаты.°「Ут
Возможные уточнения
Большой недостаток этого класса — негибкость в определении функции / н@ пример, чтобы определить выражение у = М 十 2z3 - 3, требуется длинна^ инструкция подобно у = х*х*х*х*х + 2*х*х*х - 3. Что делать, если фуНк ция содержит члены, подобные х100?
Другие недостатки вытекают из факта, что производная функция 广 не_ известна. Может быть вычислено только значение /'(&)• Это вносит сильные ограничения на полезность и применимость класса.
Эти недостатки мог,т быть преодолены путем создания более сложного класса, который описывается в следующей главе — символьного класса. Класс решает все проблемы, упомянутые выше, к тому же он имеет намного больше функциональных возможностей.
6.6. Класс векторов
Введение
Вектор ——общая математическая структура в линейной алгебре и векторном анализе (см. разд. 2.6). Эту структуру можно было бы создать, используя массивы. Однако массивы в С и C++ имеют некоторые недостатки. Они эффективно обрабатываются лишь как указатели. Поэтому имеет смысл ввести класс Vector как абстрактный тип данных. Контролируя выход за границы диапазона и перегружая математические операции (например, сложение векторов), мм построим исчерпывающий и безопасный в использовании класс Vector. С другой стороны, он вполне может заменять массивы, поддерживаемые С и C++ как совокупность объектных данных.“
Класс Vector — структура, которая обладает многими интересными свойствами. Поведение его можно суммировать следующим образом:
Класс Vector лучше реализовать как шаблонный класс, потому что 0Hl контейнерный класс, благодаря чему элементы экземпляров класса мо'У' иметь любой тип.
Строение класса Vector достаточно простое.
Доступны арифметические операторы типа +, -, *, / с векторами и ловыми константами.
Операторы присваивания и присваивания с изменением =, +=, -=, *二, перегружены. Мы можем также копировать векторы, используя операТ°^ присваивания.
т0P взятия нижнего индекса [] перегружен. Он полезен для доступа °;%онентам вектора.
К паторы равенства (==) и неравенства (!=) проверяют, совпадают ли омпоненты двух векторов.
Доступны операторы скалярного произведения векторов и векторного про- лзвзения.
_ ция length () возвращает длину вектора, в то время как функция resize О приводит вектор к указанному размеру.
Класс Matrix объявлен как дружественный класс. Это указывает, что клас- Matrix доступна закрытая область данных класса.
дОддер3киваются операторы потокового ввода и вывода векторов.
Вспомогательный файл VecNorm.h содержит различные типы операторов вычисления нормы: |МЗ IMIzJWb и функцию нормализации для векторов.
(.6.2. Шаблоны
Контейнерный класс реализует некоторые структуры данных, которые «содержат» другие объекты. Примерами контейнеров служат массивы, списки, наборы, векторы и т. д. Использование шаблонов особенно подходит для контейнеров, так как логика управления контейнером в значительной степени независима от его содержимого. В этом разделе мы увидим, как шаблоны могут использоваться для создания одной из фундаментальных структур данных в математике — класса Vector.
Контейнер, который мы реализуем, гомогенен, т. е. содержит объекты только одного типа в противоположность контейнерам, которые содержат объекты разных типов. Он также несет определенную семантическую нагрузку. Поэтому предпочтительнее, чтобы он содержал непосредственно объекты, а не ссылки на объекты.
ИмеютсяИмеются6・6.3・ Поля данных
Имеются
Имеются
только два поля данных в классе:
* Переменная size, хранящая длину вектора.
* Переменная data — указатель на шаблонный тип Т, используемый для хранения компонентов вектора. Память распределяется динамически в соответствии с потребностями приложения. Для типов данных, которые требу- 10T большого объема памяти, желательно освобождать память, как только °на становится не нужна. Это невозможно при статическом распределе- аии памяти. Поэтому типы данных, базирующиеся на массивах (например, вектор^ и матрицы), должны использовать механизм динамического рас- ^еделения памяти для хранения своих данных.
^^ метнм, что не существует компонент вектора с индексом за пределами О I Сов вектора; это означает, что индекс может изменяться лишь в пределах Ну£ '' * size-l. Чтобы создать векторы с индексами, начинающимися не с 'Мм можем ввести производный класс, который наследует все свойства и
поведения класса Vector и имеет дополнительное поле данных, которОе зывает иную нижнюю границу индексов векторов. Поэтому лучше。6飞 丫必、 поля данных класса Vector как protected, нежели private. Это позй^^11 потомкам класса обращаться к его полям данных. Реализацию такого * "兄‘? векторов с границей оставляем в качестве упражнения для читателей. &"@
6.6.4. Конструкторы
Всякий раз, когда объявляется массив из п векторов, транслятор автомат ски вызывает заданный по умолчанию конструктор. Поэтому чтобы обесце;" корректное использование класса, нам нужно должным образом инициализ ровать элементы данных в заданном по умолчанию конструкторе. Это вклад чает и назначение NULL в качестве значения поля data. Этот шаг критичен ^^ как мы можем столкнуться с некоторыми трудностями во время исполнения
Общая ошибка состоит в присвоении Vector-величины неинициализированному вектору с помощью оператора присваивания =, который сначала очищает старое значение (поля data) левой части оператора. Но нет никакого «старого» значения: в data очищается некоторое случайное значение, что может привести к разрушительным последствиям. Выходом является обнуление переменной data, потому что удаление указателя NULL совершенно законно и не имеет никакого побочного эффекта.
Фактически имеется два перегружаемых конструктора:
Vector(int n) Vector(int n,T value)
Первый конструктор выделяет n ячеек памяти для вектора. Второй помимо этого инициализирует компоненты вектора значением value.
Конструктор копирования Vector(const Vectorfe source) создает новый вектор, идентичный исходному вектору source. Он вызывается автоматически при необходимости создания временных копий, например при передаче параметров функции и возвращаемых значений. Он может также использоваться явно при конструировании вектора.
Ниже мы перечислим некоторые общие способы объявления вектора:
// объявление вектора из 10 чисел типа int
Vector <int> u(10);
// объявление вектора из 10 чисел, и инициализация их значением ° Vector <int> v(10,0);
// использовгпше конструктора копирования
// для создания и дублирования вектора
Vector <int> w(v);
Всякий раз, когда локальный Vector-объект выходит из оборота, вступает в игру деструктор. Он освобождает память, занятую массивом компонентов вектора, возвращая ее в кучу. Если этого не делать, память будет утерян% так как она остается распределенной, но никакой указатель уже не указывает на нее.
Операторыб'* г»「тво операторов, применимых 60mli^унарный)+ , (унарный)-, +,*,° н -=,*=,/=,==,!=, I,Операторыб'* г»「тво операторов, применимых 60mli^унарный)+ , (унарный)-, +,*,° н -=,*=,/=,==,!=, I,к векторам, реализовано в классе, ак векторам, реализовано в классе, а
Операторы
б'* г»「тво операторов, применимых 60mli^
унарный)+ , (унарный)-, +,*,
° н -=,*=,/=,==,!=, I,
Операторы
б'* г»「тво операторов, применимых 60mli^
унарный)+ , (унарный)-, +,*,
° н -=,*=,/=,==,!=, I,
к векторам, реализовано в классе, а
к векторам, реализовано в классе, а
оложим, что u, V, w —векторы, и с — числовая константа, тогда имею- операторы определяются следующим образом:
u+v u-v, uv, u/v складывает, вычитает, умножает или делит соответ* ствуюшие компоненты и и v.
и+=$, u-=v, u*=v, u/=v складывает, вычитает, умножает или делит соот~ зетствующие компоненты v и и с последующим присваиванием результата компонентам и.
.и+=с, и-=с, и*=с, и/=с складывает, вычитает, умножает или делит каж~ дую компоненту и со скаляром с с последующим присваиванием результата вектору и.
Оператор присваивания = должен быть перегружен в классе. Если опустить определение оператора присваивания, транслятор C++ сам вставит его. Однако нужно иметь в виду, что код, произведенный транслятором, просто копирует байт за байтом компоненты данных. В случае динамического распределения памяти оператор копирования «байт за байтом» копирует только адрес указателя, а не содержимое памяти, что приводит к появлению указателей, указывающих на одну и ту же ячейку памяти. Это требует соответствующих средств управления. Поэтому надежнее иметь собственный оператор присваивания. Те же самые аргументы приводят к необходимости иметь конструктор копирования векторов.
В классе имеются две формы оператора присваивания:
—u=v делает копию из v в и.
一 и=с присваивает константу с каждой компоненте вектора и.
Заметим, что оператор присваивания определен так, что он возвращает ссылку на сам объект (присваивания), тем самым позволяя конструкции типа и = V = w.
u==v возвращает истину^ если и и v содержат равные компоненты в том же самом порядке, иначе возвращает ложь.
е u! —лишь обратное утверждение для u==v.
Мы используем символ | как оператор скалярного произведения (также используется название внутреннее произведение). Оно определяется как
抬抬4 х (В х С) + В х (4 х 8) х (С х D)=А^ВхС) =4 х (В х С) + В х (4 х 8) х (С х D)=А^ВхС) =л)сX Ал)сX АXXо = 5) X X с +о = 5) X X с +р)р)X Xр)р)X XX X сX X сссНиже показаны некоторые примеры использования скалярного и вектп го произведений векторов. Предположим, что Л, В, С, D — четыре вектп »"& R?, тогда閱
抬
抬
4 х (В х С) + В х (4 х 8) х (С х D)=
А^ВхС) =
4 х (В х С) + В х (4 х 8) х (С х D)=
А^ВхС) =
л)с
X А
л)с
X А
X
X
о = 5) X X с +
о = 5) X X с +
р)р)
X X
р)р)
X X
X X с
X X с
с
с
Следующая программа демонстрирует, что эти тождества выполняются некоторых случайно выбранных векторов:
// vprod.срр
#include <iostream.h>
#include "Vector.hM
void main()
Vector<double> A(3), B(3), C(3), D(3);
A[0]=1.2;A[l]=1.3;A[2]=3.4;
B[0]=4.3;B[l]二4.3;В[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 « A%(B%C) + B%(C%A) + C%(A%B) « endl;
cout « (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 << (А|В%С) - (А%В|С) << endl;
Результат
[1.42109е-14]
[0]
[0]
[372.619]
[376.034]
[540.301]
[372.619]
[376.034]
[540.301]
п72・6初
ненулевое значение 1.42109е-14 вызвано погрешностями округления. Та- М"壊 азом чтобы получить правильный результат, а именно нулевой вектор, М Сдользуем тип данных Vector<Rational<int> >.
6 6.6. Методы и нормы
арифметических операторов, в классе Vector есть еще несколько полез- ^^ функций и методов. Их определения и свойства перечислены ниже: .функция length О возвращает размерность вектора.
.resize(int п) устанавливает размерность вектора равной п. Компоненты при этом не изменяются, за исключением того, что, если новая размерность меньше начальной, то лишние хвостовые компоненты удаляются, а если больше, то добавляются новые хвостовые неинициализированные компоненты.
• resize(int n, value T) действует как предыдущий метод, кроме случая, когда новая размерность больше начальной: в этом случае дополнительные компоненты инициализируются значением Т.
Во вспомогательном файле VecNorm.h мы реализуем три различные векторные нормы и функцию нормализации:
• norml(x)• norml(x)• norm2(x)double.• norml(x)• norm2(x)double.• norml(x)определяется как ||x||i := Е| + 电丨 + …+ |xn|.
• norml(x)
• norml(x)
• norm2(x)double.
• norml(x)
• norm2(x)double.
• norml(x)
определяется как ||х||2 :=,若 + £§ + ... + , тип norm2()—
определяется как ЦхЦоо :二 max{|xi|, |т2|,... , |хп|}.
е функция normalize (х) используется, чтобы нормализовать вектор х. Нормализованная форма вектора х определена как х/|х|, где IхI есть 2- Н0Рма х.
ЧтобыЧтобыторыеторыеиметь лучшее представление об этих функциях, рассмотрим некоиметь лучшее представление об этих функциях, рассмотрим некопримеры:примеры:〃 ^110ml. схх〃 ^110ml. схх
Чтобы
Чтобы
торые
торые
иметь лучшее представление об этих функциях, рассмотрим неко
иметь лучшее представление об этих функциях, рассмотрим неко
примеры:
примеры:
〃 ^110ml. схх
〃 ^110ml. схх
aclu4e <iostreain.h> Delude "Vector.h" "elude "VecNorm.h"
norml() norm2() norml() norml() norm2() norml() norm2() norm2()norml() norm2() norml() norml() norm2() norml() norm2() norm2()
norml() norm2() norml() norml() norm2() norml() norm2() norm2()
norml() norm2() norml() norml() norm2() norml() norm2() norm2()
Vector<int> V;
v.resize(5,2);
cout << *'Размерность вектора v равна " << v. length() << endl ■ cout << endl;
Vector<double> a(4,-3.1), b;
b,resize(4);
b[0] = 2.3; b[l] = -3.6; b[2] = -1.2; b[3] = -5.5;
// Различные векторные нормы
cout
<< "norml()
of a =
"<< norml(a) << endl;
cout
<< nnorm2()
of a =
'* « norm2(a) « endl;
cout
<< "norml()
of a =
"<< norml(a) << endl;
cout
« endl;
cout
<< "norml()
of b =
"<< norml(b) << endl;
cout
« "norm2()
6f b =
"<< norm2(b) << endl;
cout
<< "norml()
of b =
"<< norml(b) << endl;
cout
<< endl;
// Норма norm2() нормализованных векторов а и b равна 1
cout « "norm2() нормализ. а = h « norm2(normalize(а)) << endl;
cout << "norm2() нормализ. b = " << norm2(normalize(b)) << endl;
Результат
Размерность вектора v равна 5
of a = 12.4 of a = 6.2 of a = 3.1 of b = 12.6 of b = 7.06682 of b = 5.5 нормгшиз. a = 1 нормализ. b = 1
Потоки
Перегруженный оператор потокового вывода « просто выдает все компоненты вектора v и помещает их между парой квадратных скобок [и。, и】,曾2,【卜
Оператор потокового ввода >> сначала читает и залисывает в поле 耳护 размерность вектора, затем считывает соответствующее количество компонент вектора.
Класс матриц
.Введение
ы —двумерные массивы с некотором числом строк и столбцов. Они — струКТурь1 в линейной алгебре. Строить матричный класс мы будем не ре31cs^0M месте. Мы используем преимущества (возможность многократного ” \ткзования и расширяемость) объектно-ориентированного программирова-
^«тобы формировать новый класс, основанный на классе Vector. Вектор — 吗二^ случай матрицы с числом столбцов, равным одному. Таким образом чаС^^ем определить матрицу как вектор векторов.
template <class Т> class Matrix
// Поля данных
int rowNum, colNum;
Vector<T> mat;
}
Мы объявили Matrix как шаблонный класс. Определение матрицы как вектора векторов имеет то преимущество, что методы матричного класса могут использовать многие из векторных операций, определенных для векторного класса. Налример, мы могли произвести векторное сложение в каждой строке матрицы, чтобы получить результат матричного сложения. Это сокращает объем повторного кода.
Чтобы эффективно использовать матричный класс, пользователи должны ознакомиться с поведением и интерфейсами класса. Ниже мы суммируем их:
• Метод resizeO переопределяет числа строк и столбцов согласно
значениям, поступившим в параметрах функции и размещает в рицу.
пам^тн
Нои
• Методы row() и cols() возвращают число строк или столбцов соответственно.
матрн%
Поддерживаются потоковые операторы ввода (») и вывода («).
Вспомогательный файл MatNorm.h содержит три различные матр^д нормы: 1И111, ЩНоо, |Щ|н.1
Поля данных
Поля данных rowNum и colNum определяют число строк и столбцов матрицы соответственно.
Vector<T>* mat хранит элементы матрицы. Она объявлена как вектор Век, торов. Чтобы выделить память для m х п матрицы, мы должны сначала раз. местить вектор из m указателей на вектора. Затем для каждого указателя мы размещаем снова вектор размерности п. В итоге под матрицу отводится m х п ячеек памяти. После соответствующей инициализации матрица готова к использованию.
Конструкторы
В классе имеется два способа создать матрицу. Единственный критерий для существования матрицы — спехщфицирование числа строк и столбцов:
Matrix() объявляет матрицу без спецификации размерностей. Такая матрица не пригодна для использования. Чтобы активировать матрицу, мы используем метод, называемый resize(), который переразмещает матриц с числом строк и столбцов, задаваемых в параметрах функции.
Matrix(int nr, int nc) объявляет nr*nc матрицу с неинициализированными элементами.
Matrix(int nr, int nc,T value) объявляет nr*nc матрицу с элементами, инициализированными значением value.
Matrix(const Vector<T>& v) создает матрицу из вектора v. Подразумевается, что результирующая матрица будет иметь только один столбец.
Конструктор копирования дублирует матрицу. Он вызывается автоматически транслятором, когда необходимо, и также явно может быть вызван пользователями.“
Деструктор возвращает неиспользованную память назад в пул свободной памяти.
Ниже приведены некоторые примеры объявлений матрицы:
// Объявить 2x3 матрицу типа int
Matrix<int> m(2,3)
// Объявить 3x4 матрицу и инициализировать ее элементы значением 5
Matrix<int> п(3,4,5)
используя конструктор копированияиспользуя конструктор копирования,"бляровать матрицу, 〃 ^x<int> Р(п);
используя конструктор копирования
используя конструктор копирования
// 'vg// 'vgматрицу q из вектораматрицу q из вектораиздать,tor<double> v(3,0); ',ix<double> q(v);издать,tor<double> v(3,0); ',ix<double> q(v);vv6 7.4. Операторы6 7.4. Операторыдеется много матричных операторов, реализованных в классе, а именнодеется много матричных операторов, реализованных в классе, а именно(увар(увар)НЫЙ)+, (унарный)-,)НЫЙ)+, (унарный)-,+, -, *, /+, -, *, /•=, *=, /=, LI,•=, *=, /=, LI,О,=О,=
// '
vg
// '
vg
матрицу q из вектора
матрицу q из вектора
издать
,tor<double> v(3,0); ',ix<double> q(v);
издать
,tor<double> v(3,0); ',ix<double> q(v);
v
v
6 7.4. Операторы
6 7.4. Операторы
деется много матричных операторов, реализованных в классе, а именно
деется много матричных операторов, реализованных в классе, а именно
(увар
(увар
)НЫЙ)+, (унарный)-,
)НЫЙ)+, (унарный)-,
+, -, *, /
+, -, *, /
•=, *=, /=, LI,
•=, *=, /=, LI,
О,=
О,=
Некоторые из операторовНекоторые из операторовперегружаемы с более чем одним определением!
Некоторые из операторов
Некоторые из операторов
Пользователям советуем перед использованием класса тщательно читать документацию.
Ниже мы обсуждаем поведение и использование операторов. Предположим,
4To А, В— матрицы, V— вектор, с — числовая константа.
Операторы А+В, A-В и А*В складывают, вычитают и перемножают две матрицы согласно их обычным определениям.
Операторы А+с, А-с, А*с и А/с определены как А+с1, А-cl, А*с1 и А/с! соответственно, где I — единичная матрица.
Операторы с+А, с-А, с*А и с/А имеют аналогичные определения, как выше.
А=В делает в А дубликат из В, а А=с присваивает с всем элементам матрицы А.
А+=В, А-=В и А*=В — лишь модификации оператора присваивания, которые осуществляют две операции на одном операторе. Например, А+=В эквивалентно выполнению А=А+В.
А+=с, А-=с, А*=с и А/=с — только модифицированная форма оператора присваивания. Например, А+=с эквивалентно выполнению А=А+с1.
Функция vec(A) используется для создания вектора, который содержит элементы матрицы А, один столбец за другим. Предположим
тогда
vec(A)vec(A)2 О £
vec(A)
vec(A)
3а-3
Р°некерово произведение двух матрицы описано в разд. 2.12. Функция 4 °п(А,В) используется для вычисления кронекерова произведения матриц и В. Заметим, что в общем случае А ^ В ^ В 0 А (если А 0 В и В (^ А 『еют одинаковые размерности) и (4 区 В)区(С 区 0) = (АС)区{BD) (если совместима с С и В совместима с Z> по размерностям).
1154st
Оператор нижнего индекса [] перегружен и означает обращение к во строки матрицы. Например, A[i] возвращает i-ю строку матрицы вектор.
©Ру 心к©Ру 心кОператор верхнего индекса () перегружен и означает обращение к вект столбца матрицы. Например, B(j) возвращает j-й столбец матрицы в(
©Ру 心к
©Ру 心к
вектор.
и неравенства (!=) проверяют, совпадают ли неравенства (!=) проверяют, совпадают лОператоры равенства (==) элементы двух матриц.
и неравенства (!=) проверяют, совпадают л
и неравенства (!=) проверяют, совпадают л
Операторы == и ! = имеют более низкое старшинство, чем оператор потоку вого вывода «. Это означает, что требуются скобки при записи инструкцИи подобных следующим:'
cout << (u != V) << endl;
cout << (u == v) << endl;
в противном случае транслятор будет жаловаться на них.
Методы и нормы
В класс включено много полезных методов. Их свойства описаны ниже:
transpose () mxn матрицы А представляет собой nхт матрицу Ат, такую, что элемент ij матрицы 4T является элементом ji матрицы А.
trace () п х п матрицы — это сумма всех диагональных элементов A, trA :二 a11 +a22 + …+ ttnn.
determineint () — метод для вычисления детерминанта матрицы был описан в разд. 2.13. Используемый алгоритм зависит от того, является ли матрица символьной или числовой. Так как наша система, как предполагается, работает с символьными выражениями, мы используем метод JTeBepbe(Leverrier) для вычисления детерминанта.
inverseO используется, чтобы получить обратную матрицу (если матрица обратима). Алгоритмы, использованные для нахождения обратной матрицы, различны для числовой и символьной матриц. Для числовой матрицы мы можем использовать LU-разложение [40] и метод обратных подстановок, а для символьной матрицы мы используем метод Леверье. Этот мет°2 может также использоваться и для числовой матрицы.
resize() переопределяет число строк и столбцов согласно новым специф^ кациям,задаваемым параметрами функции,и размещает матрицу в памяти-
rows() и cols() возвращают число строк и столбцов матрицы, соответственно.
Во вспомогательном файле MatNorm.h мы реализовали три различные матр"”" ные нормы:
11ЛЦ1 := max1<><п11ЛЦ1 := max1<><пnorml (А) определена как максимальное значение сумм абсолютных вел и ч1111 элементов по столбцам,
11ЛЦ1 := max
1<><п
11ЛЦ1 := max
1<><п
г=1
т (а) определена как максимальное значение сумм абсолютных величин • Цементов по строкам,
Mk :=m^ £|av|
огтН(А) — норма Гильберта—Шмидта, определяемая как
п т
\\А\\Н := [trA*A]1/2 = [trAA*]1/2 =,\ £Е%#・ \ £=I j=l
Цример ]
В этом примере демонстрируется использование кронекерова произведения ма- 1Pm. Мы объявляем и определяем четыре матрицы и затем вычисляем произведение Кронекера.
// kron.cxx
#include <iostream.h>
•include "Matrix .h"
void main ()
Matrix<int> A(2,3), B(3,2), C(3,l), D(2,2);
AtO] [0]=2;A[0] [1]=-4;A[0][2]=-3;
АШ CO]=4;A[l] [1]=-1;A[l][2]=-2;
BEO] [0]=2;B[0] [1]=-4;
B[l][0]=2;B[l] [1]=-3;
В⑵[0] = 3; В⑵[1] = -1;
С[0] [0] = 2;
С[1"0] = 1;
С⑵期=_2;
沪[0] = 2; D[0][l] = 1;
D⑴[0] = 3; D[l] [1] = -1;
cout « kron(A,B) << endl;
Sut << kron(B,A) << endl;
} c°ut « kron(A,B)*kron(C,D) - kron(A*C,B*D) « endl;
Result
[4 - 8 - 8 16 - 6 [4 -6 -8 12 -6 [6 -2 -12 4 -9 [8 -16 -2 4 -4 [8 -12 -2 3 -4 [12 -4 -3 1 -6
12]
9]
3]
8]
6]
2]
[4 - 8 - 6 - 8 16
[8 -2 -4 -16 4
[4 -8 -6 -6 12
[8 -2 -4 -12 3 [12 -3 -6 -4 1
12]
8]
9]
6]
2]
[О 0]
[0 0]
[0 0]
[0 0]
[0 0]
[0 0]
Пример 2
В )ГОМ npiK¥T「Mi,l :1<-'.и Ufi TplIpyt'.1.!
tr (АВ) =tr (BA) tr (AB) otr (A) tr (B)
//trace.xxx
#include <iostream.h>
#include "Matrix.h"
void main()
{
Matrix<double> A(3,3),B(3,3,1);
A[0] [0] =2;A[0][1]=-l;A[0][2]=1;
A[l] [0] =1;A[l][1]=-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/х.'/.к с лги;/Hfii
cout << " tr (AB) = " << <out<< (A*B) . t-race () <<endl ;
cout << "tr(BA) = " << cout<< (B*A).trace() <<endl;
cout << "tr(A)tr(B) = " << A.trace() * В.trace() << 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
llpHSK-p :;
В t i i < ирн?лг[; •::匸 it-'.? 41''! jiiifiy-'M in i^i -/и. it -влип1 M<TrkbL de tern: in;
#include <iostream.h>
#include "Matrix.h"
void main()
{
Matrix<double> A(2,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;
cout << endl; }
Результат
[1 2]
[3 4]
Детерминант матрицы = -2
[1 2 3]
[3 4 3]
[3 3 3]
Детерминаят матрицы = -6
[1
[3
2 3 4]
4 3 4]
[3
3 3 4]
[4
4 4 4]
Детерминаят матрицы = 8
6.8. Классы массивов
Введение
Массив — общая структура данных в программировании. Языки С и C++ снабжены встроенными структурами данных массива, которые имеют некоторые недостатки. Например, не контролируется выход за границы массива для предотвращения возможных ошибок во время выполнения. Поэтому полезно ввести класс Array как абстрактный тип данных. Мы построим исчерпывающ!iн класс Array контейнерного типа с контролем границ, перегружая некоторые простые математические операторы (например, добавление массива). С другой ('тороны, он может заменить массив, поддерживаемый С и C++ как совокупность объектов данных.
В этом разделе мы реализуем одно-, дву-, трех- и четырехмерные массивы. Каждый п+1-мерный массив использует методы и операторы n-мерною 1】х близкая зависимость видна очевидным образом в полях данных:
template <class Т> class Arrayl template <class T> class Array2
{{
private:private:
// Поля данных// Поля данных
int n.data;int rows, cols;
T *data;Array1<T> *data2D;
};};};
};
};
,7a <class Т> class АггауЗ temP^te,7a <class Т> class АггауЗ temP^teivate:// Поля данных jut rows > cols, lews; Array2<T> *data3D;ivate:// Поля данных jut rows > cols, lews; Array2<T> *data3D;template <class T> class Array4private:// Поля данныхint rows, cols, lews, blks;Array3<T> *data4D;template <class T> class Array4private:// Поля данныхint rows, cols, lews, blks;Array3<T> *data4D;
,7a <class Т> class АггауЗ temP^te
,7a <class Т> class АггауЗ temP^te
ivate:
// Поля данных jut rows > cols, lews; Array2<T> *data3D;
ivate:
// Поля данных jut rows > cols, lews; Array2<T> *data3D;
template <class T> class Array4
private:
// Поля данных
int rows, cols, lews, blks;
Array3<T> *data4D;
template <class T> class Array4
private:
// Поля данных
int rows, cols, lews, blks;
Array3<T> *data4D;
};};};
};
};
Здесь мы сильно упростили конструкцию класса, а код сделали более спективным и информативным. В очередной раз мы продемонстрирова- мопхь объектно-ориентированного программирования в плане повторного использования кода и расширяемости.
Прикладные программы для одно- и дву- мерных классов Array общие. Причины расширения класса Array на три и четыре измерения состоят в том, 4To некоторые прикладные программы для тензорных полей нуждаются в 310M. Например, символы Кристоффеля требуют трехмерного класса Array,
а для тензора кривизны мы нуждаемся в четырехмерном классе Array.
В этом разделе описываются четыре класса Array (мы назвали их как Array 1)Аггау2, АггауЗ и Аггау4), которые предназначены для классов одно-, дву-, трех- и четырехмерных массивов, соответственно. Хотя они различаются в определениях и функциональных возможностях, они используют похожие интерфейсы. Поэтому описанное ниже относится ко всем четырем классам, кроме некоторых незначительных различий, которые будут отмечены по ходу
описания.
Поля данных
Имеются только два поля данных в каждом классе. Это ——размерности mHCCji и указатель на элементы массива.а
Класс одномерных массивов Arrayl содержит только целую переменНу n_data, которая определяет размер массива. Он также содержит указать на непрерывный массив Ts.
Память распределяется динамически так, чтобы удовлетворять потреб ности прикладных программ. Для типов данных, которые требуют больщ^ го объема памяти, желательно освобождать память, как только она больще не нужна. Это невозможно со статическим распределением памяти. Поэто. му типы данных на основе массива должны использовать механизм динамического распределения памяти для хранения данных.
Класс двумерных массивов Аггау2 требует двух целых чисел — rows и cols для определения их размеров, а также указатель на Arrayl. Чтобы разме стить в памяти mxn массив, мы сначала размещаем массив из m указателей на Arrayl, затем для каждого указателя размещаем массивы размера п.
Классы трех- и четырехмерных массивов АггауЗ и Аггау4 требуют, соответственно, три и четыре целые переменные, определяющие их размеры. АггауЗ содержит указатель на массив класса Array2, а Аггау4, соответственно —на массив класса АггауЗ. Эти указатели используются для распределения памяти, требуемой для хранения элементов массивов. Процедуры для распределени я памяти для этих указателей следуют той же самой логике, как для двумерных массивов, описанной выше.
Конструкторы
Детализация для конструктора, заданного по умолчанию, важна, особенно когда поля данных класса включают некоторые указатели или ссылки. Важно инициализировать элементы данных в классе, включая пустые указатели. Это предотвращает появление некоторых проблем на более поздних этапах разработки. Каждый класс имеет два разных перегруженных конструктора:
Arrayl(int)Arrayl(int,Т value)
Array2(int,int)Array2(int,int,T value)
АггауЗ(int,int,int)АггауЗ(int,int,int,T value)
Array4(int,int,int,int)Array4(int,int,int,int,T value)
Первый набор конструкторов выделяет соответствующий объем памяти 二〔」' массивов класса, в то время как второй, кроме того, инициализирует все :<1е" менты значением value. Конструктор копии и деструктор должны быть ным образом реализованы для класса, имеющего указатели в полях данных,
Ниже мы даем несколько примеров использования конструкторов:
// Объявить и, как одномерный массив размера 20, // элементы неинтщализированы.
Arrayl<int> u(20);
дб-ьявить V, как двумерный массив размера 2x3, // ементы инициализированы значением нуль. 〃不吋2< double〉v (2,3,0.0);
о6ъЯВИТь w, как трехмерный массив типа double, /; с размером 2x3x2, все его элементы
彳;11sициал^зировань! значением 2.0
Array3< double〉w(2,3,2,2.0);
〃 объявить х, как четырехмерный массив размера 1х2хЗх4, 〃 все элементы в массиве неинициализированы.
Arp4c double> х(1,2, 3,4);
// Конструктор копирования вызьшается для дублирования массива. АггауЗ y(w);
Операторы
Некоторые общие операторы перегружены в классе, а именно (унарные)+ и +,*, =, +=, -=, *=, ==, ! = , [] , <<, и ». Предположим, что
口,V, w— массивы, с — константа, тогда операции определены следующим образом:
+и не оказывает никакого эффекта на и, в то время как -и меняет знак каждого элемента и.
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-й элемент массива. Так Qk возвращается ссылка, то конструкция может использоваться с обеих Сторон выражения присваивания.
Оператор потокового вывода « просто выводит все элементы и заключает "X в квадратные скобки.
Например, массив и класса Array 1 форматируется как [乜0 〃1 • • • Un_data-1 ],
а массив v класса Аггау2 форматируется как
^00
^01• • •^0 cols
^11■ • ■^1 cols
^rows 0
^rows 1• • • ^rows cols
Вывод для АггауЗ менее очевиден. Массив можно рассматривать ^ куб элементов массива. Результат вывода отформатирован таким образом что каждый уровень куба выводится как массив класса Аггау2 и отделяется пустой строкой. Образец АггауЗ w(2,3,2) будет напечатан как
[Wooo Wool ] [Wow won ] [W020 ^021 ]
[goo wioi ] [W11O Win ] [3120 W121 ]
Идея вывода массива АггауЗ расширена на Аггау4. Четырехмерный массив можно визуализировать как совокупность трехмерных блоков АггауЗ. Поэтому при печати массива Аггау4 мы печатаем блоки АггауЗ, следующие один за другим, пока все четвертое измерение массива не будет исчерпано. Как пример, мы печатаем массив Аггау4 х(2,2,2,3)
[^0000 [^0010
^0001
^0011
^0002 ]
^0012 ]
[^0100
^0101
^0102 ]
[^0110
^0111
^0112 ]
[^1000
^1001
^1002 ]
[^1010
^1011
^1012 ]
[3100
^1101
6102 ]
[^1110
^1111
^1112 ]
Каждый уровень в АггауЗ отделен пустой строкой в то время, как каждн” блок в Аггау4 отделен двумя пустыми строками.
Оператор потокового ввода >> — только обращение оператора потокового вывода. Он читает размерности каждого измерения массивов, затем 2T тывает индивидуальные элементы данных, пока все элементы не будут вР€^ дены.
6$ 5. Методы
' г доступные для каждого массива класса, включают:
Мет。*% а
size(int = °) const; Возвращает число элементов в каждом измере- • 1П „ля каждого класса. Например, size(O) возвращает размерность для ^ ау17 или количество строк для Аггау2, АггауЗ и Array4; size(2) возвращает количество уровней для АггауЗ и Аггау4, а для Array 1 и Аггау2 не определен.
Заметим, что задание параметра по умолчанию используется для функ- дни; это указывает, что, если параметр опущен, то возвращается размер нулевого измерения для каждого массива.
.void resize(int) ; Это — важная функция для массивов класса. Следующая инструкция
Array1 <int> u;
объявляет переменную и, но память под нее не выделяется. Такая переменная не пригодна для использования. После выполнения инструкции
u.resize (100);
транслятор размещает 100 элементов массива и в памяти. После этого переменная к использованию готова. Если мы хотим заполнить массив неко торыми начальными значениями при размещении в памяти, применяется другая форма функции:
void resize(int n, value T);
В дополнение к распределению памяти все элементы инициализируются значением value. Для Аггау2, АггауЗ и Аггау4 метод работает аналогично, за исключением того, что resizeO требует два, три или четыре параметра для задания размеров по соответствующим измерениям массивов.
Функция resizeO фактически может использоваться для изменения размерностей массивов в процессе использования. Увеличение размерности массива привело бы к переносу старого содержимого памяти, копируемого в новый массив с увеличенным размером, неопределенного или инициализированного, в зависимости от того, какая форма resizeO используется. Когда resize() сокращает массив, только соответствующая часть старой памяти будет скопирована в новый массив, и дополнительные элементы будут «усечены».
Следующая программа демонстрирует использование функции resizeO : 〃 aresize.схх
"include <iostream.h>
"include "Array.h"
;oid main()
Arrayl<double> M;
M.resize(3);
М[0] = 5.0; М[1] = 8.0; М[2] = 4.0;
cout « "М = \п“ << М << endl; cout « endl;
Array2<double> A;A.resize(2,3);A[0] [0] = 1.0; A[0] [1]A[l] [0] = 4.0; A[l] [1]Array2<double> A;A.resize(2,3);A[0] [0] = 1.0; A[0] [1]A[l] [0] = 4.0; A[l] [1]=3.0; A[0] [2] = 2.0;=5.0; A[l] [2]二 6.0;=3.0; A[0] [2] = 2.0;=5.0; A[l] [2]二 6.0;
Array2<double> A;
A.resize(2,3);
A[0] [0] = 1.0; A[0] [1]
A[l] [0] = 4.0; A[l] [1]
Array2<double> A;
A.resize(2,3);
A[0] [0] = 1.0; A[0] [1]
A[l] [0] = 4.0; A[l] [1]
=3.0; A[0] [2] = 2.0;
=5.0; A[l] [2]二 6.0;
=3.0; A[0] [2] = 2.0;
=5.0; A[l] [2]二 6.0;
A.resize(3,4>2);
cout << "A = \nn << A << endl;
A.resize(2,2);
cout << "A = \nH << A << endl;
A.resize(4,4,9);
cout « "A = \n" << A << endl;
Результат
M =
[5 8 4]
A =
[1 3 2 2]
[4 5 6 2]
[2 2 2 2]
A =
[1 3] [4 5]
A =
[1 3 9 9]
[4 5 9 9]
[9 9 9 9]
[9 9 9 9]
6・9・ Класс строк
В традиционном С строка представлена как указатель символьного типа. Это означает, что строку можно рассматривать как массив символов. В этом пр(讣 ставлении конец строки отмечается символом '\0‘. Это соглашение имеет <№ щественный недостаток: символ '\0‘ занимает один байт памяти и 只вляг】( " частью строки. Например, строка "abc" требует 4 байта памяти. Несоотв<4* ствие между длиной строки и размером занимаемой строкой памяти внзьн'г ет замешательство многих пользователей и является источником ошибок 丄用 них. Поэтому, чтобы избежать возможных ошибок и повысить эффективнее и,
х операций, лучше всего реализовать класс строк как абстрактный 「тр°кКлзм с строк является стандартным в C++.
Введение
ПоведениеПоведениекласса суммарно описывается следующим образом:
Поведение
Поведение
Конструирование строк просто.
Экземпляры класса String дублируются оператором присваивания. Оператор взятия индекса [] применительно к строкам обеспечивает доступ к отдельным символам строки.
Оператор сложения + означает для строк их конкатенацию.
Доступны операторы сравнения для строк: >, >=, <, <=, ==, !=.
При необходимости строки могут быть преобразованы к const char*, reverse О изменяет порядок символов строки на обратный.
length () возвращает длину строки.
Поддерживаются операторы ввода и вывода строк.
Поля данных
В классе имеются два поля данных:
datalength хранит максимум возможной длины строки.
data содержит указатель на объект типа char. Когда строка размещается в некоторой области памяти, оно будет использоваться для хранения символов строки.
Конструкторы
Строки создаются и инициализируются следующим образом:
String х; String у=0; String z="";
х, у и z суть пустые строки. Заметим, что 0 и " " могут использоваться как NULL-строка.
String х('А'), у('#‘); устанавливает х и у равными А и #, соответственно.
String x("abc") ; String y="abc"; устанавливает х и у равными строке "abc".
String x(int n); размещает в памяти NULL-строку х длиной п байт.
String u(x); String v=x; вызывается конструктор копирования, чтобы Дублировать х в и и V.
Деструктор используется для освобождения неиспользованной памяти °братно в кучу.
6・9・4. Операторы
R
^асс были включены некоторые общие операторы, а именно
口,+, ==,! = , >, >=, <, <=, >>, «•
Их свойства описываются следующим образом:
Оператор присваивания = делает дубликат строки.
Оператор взятия индекса [] обеспечивает доступ к отдельным симв строки.°"咖
Оператор конкатенации + соединяет две строки в одну.
Шесть операторов сравнения ==, ! = , >, >=, <s <= сравнивают уПог)я ченные лексикографически строки, используя функцию str стр ().“、
Операторы потокового ввода и вывода дают способ импорта и экспорт экземпляров строк.
Оператор преобразования типов автоматически конвертирует строку Во встроенный тип const char*, когда это необходимо.
Методы
Имеется лишь два метода, реализованных в классе — length() и reverse():
length() возвращает длину строки.
reverse() изменяет порядок следования символов строки на обратный и возвращает обращенную строку в качестве результата.
Описание большинства операций и методов очевидно и может быть легко понято. Оператор преобразования к встроенному типу const char* играет важную роль в классе. Он позволяет использовать с классом все необходимые функции из библиотеки <string.h>. В силу этого мы избегаем дублирования кода, хотя некоторые из функций могли бы быть заново реализованы в лучшей форме. Мы демонстрируем использование класса в следующих примерах:
// sstring.схх
#include <iostream.h>
#include "MString.h"
void main()
String Sl(nChildren");
cout << " Строка SI есть ” << SI << endl;
cout << *' Отражение строки Si есть ” « S1.reverse() << endl;
cout « ” Длина строки Si есть ” << SI.length() « endl;
cout << endl;
String S2 = "Mother";
String S3(S2);
S3[0] = F ;
S3[l] = & ;
cout << " Строка S2 есть " << S2 « endl; cout « 11 Строка S3 есть 11 << S3 << endl;
cout « endl;
// Конкатенация строк
oUt << S2 + " and " + S3 << endl;
;out « S3 + " and ” + S2 « endl; cOut « endl;
〃 Сравнение строк
cout << (S2 == S3) << endlj
c0Ut « (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
0
1
0
1
6・9.6・ Оператор преобразования типа
В классе имеется один оператор преобразования типа, а именно преобразование к встроенному типу string языка С (char*). Этот оператор производит преобразование автоматически, когда это необходимо. Он может также быть вьтзван явно при использовании понятия каста (см. разд. 5.5). Это преобраз о- ванне становится полезным при использовании библиотечных подпрограмм из 'string.h>, таких, как strlenO? strcpyO, strtokO, и т. д.
6・9・7. Возможные усовершенствования
Р^евидно, что класс может быть улучшен и расширен. Например, можно до- авнть функции, работающие с подстроками и функции модификации строк. Услуживают реализации также поиск вхождения и сравнение строк. НаприМер, вырезка подстроки, которая обеспечивает возможность обратиться к ча- 5< строки, может быть перегруженным оператором круглых скобок (). Опера-
тор имел бы два параметра, где первый определял бы левый индекс подсцтор имел бы два параметра, где первый определял бы левый индекс подсца второй — правый индекс. Следующий пример иллюстрирует это расширение:а второй — правый индекс. Следующий пример иллюстрирует это расширение:ВОЗМи*ВОЗМи*」1(用」1(用
тор имел бы два параметра, где первый определял бы левый индекс подсц
тор имел бы два параметра, где первый определял бы левый индекс подсц
а второй — правый индекс. Следующий пример иллюстрирует это расширение:
а второй — правый индекс. Следующий пример иллюстрирует это расширение:
ВОЗМи*
ВОЗМи*
」1(用
」1(用
String s("Бриллиантовое кольцо");
cout « s(0,8) << endl;// вьшод => Бриллиант
cout << s(15,20) << endl; // вывод => кольцо
Реализацию этого расширения оставляем читателям в качестве упражнения
Битовые наборы
Битовый набор — просто вектор, компоненты которого имеют в качестве значений 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()к сильно уменьшается.
Введение
Для того чтобы битовые наборы были полезны, они должны быть удобны использовании и должны обеспечивать доступ к отдельным битам. Ниже суммируется содержание абстракции класса BitVector:
экземпляры BitVector легко конструируются указанием требуемого разм(" ра набора.
size() возвращает размер битового набора.
set (), clear (), test О и flip() производят соответствующие операц!111 над отдельным битом, который задан в параметрах.
Поддерживается потоковый оператор вывода.
2・ Поля данных
°' содержит два поля данных — bsize и data. Поле bsize хранит размер К朋“ г0 набора, тогда как data есть вектор типа unsigned char, использу- 60Tдля хранения значений битов. Здесь мы можем не задумываться о ди- еМ%ческом распределении памяти и удалении, так как об этих операциях мы Н^1^0Tli刀11cb в классе Vector, который был ранее сконструирован и тщательно ^тестирован.
Конструкторы
Имеется два возможных способа создания битовых наборов:
.BitVector (unsigned int num) определяет размер num битового набора и размещает необходимое количество векторов, чтобы разместить все биты, unsigned char содержит 8 битов, поэтому это число есть наименьшее, чье произведение с 8 больше или равно num. Этот конструктор инициализирует все биты набора пулевым значением.
BitVector (unsigned int num, unsigned int value) делает то же самое, что и предыдущий конструктор, кроме установки начальных значений битового набора в соответствии с битами параметра value.
Конструктор копирования и деструктор просто вызывают код, обеспечиваемый классом Vector.
Методы
Предположим, нто v > и — наборы из BitVector размера N и М положительных целых чисел, тогда
v.sizeO возвраш,ает размер битового набора, установленный пользователем.
V.reset(М) переопределяет размер BitVector к М и инициализирует все биты значением 0.
V• reset(М, flag) делает то же самое, кроме инициализации: биты приобретают значение параметра flag (0/1).
v-set (i) устанавливает значение i-ro бита.
v-clear (i) очищает i-й бит.
v・test(i) проверяет, установлено ли значение i-ro бита или очищено.
v-flip(i) зеркзьльно отражает значение i-ro бита (0 -> 1 и 1 -> 0). v-unionSet (и) формирует объединение наборов v и и.
v-intersectSet (и) формирует пересечение наборов v и и.
v-differenceSet (и) формирует разность наборов v и и.
v' subset (и) 1 цюверяет,является ли и. подмножеством v.
.проверяет, совпадают ли v и и.
<< у помещает v в выходной поток.
V .. ^™^ операциях, как v.subset(u), v.unionSet(и), v. intersectSet (и), Нак ferenceSet (и) и v==u, размеры битовых наборов u, v должны быть оди- bi・ Ниже мы демонстрируем использование этих методов:
// sbitvec.схх
#include <iostream.h>
#include "Bitvec.h"
void main()
// Установка битового набора размера=30;
// все биты уснанавливаюнсз! в 1 BitVector В(30,1);
// Дублирование битового набора А
BitVector А(В);
// Битовые операции A.clear(5); А.clear(20);В.clear(5); B.clear(lO);B.flip(O); B.flip(l);// Битовые операции A.clear(5); А.clear(20);В.clear(5); B.clear(lO);B.flip(O); B.flip(l);A.clear(29);B.clear(12); В.clear(29);A.clear(29);B.clear(12); В.clear(29);
// Битовые операции A.clear(5); А.clear(20);
В.clear(5); B.clear(lO);
B.flip(O); B.flip(l);
// Битовые операции A.clear(5); А.clear(20);
В.clear(5); B.clear(lO);
B.flip(O); B.flip(l);
A.clear(29);
B.clear(12); В.clear(29);
A.clear(29);
B.clear(12); В.clear(29);
cout << "Размер битового набора А есть " << A.size() << endl; cout << endl;
cout << ** A = “ << A << endl;
cout << " В = " << В << endl;
cout << ”" << endl;
// Операции над наборами битов
cout <<"объединение А иВ = n<<A.unionSet(В)<<endl;
cout <<"пересечение А иВ 二"<<А.intersectSet(В)<<endl;
cout <<11разность А и В= "<<А. differenceSet (В)<<endl;
cout << endl;
cout <<"Является А подмножеством В ? n « A.subset(В)<<endl;
cout <<"Совпадают А и В?"<< (А==В)<<endl;
Результат
Размер битового набора А есть 30
А = 111110111111111111110111111110
В = 001110111101011111111111111110
объединение А и В = 111110111111111111111111111110 пересечение А и В = 001110111101011111110111111110 разность А и В = 110000000010100000000000000000
Является А подмножеством В ? 0
Совпадают А и В ? 0
функции, реализованные без доступа к ними mask (). Они объявлены как закрытые ме-функции, реализованные без доступа к ними mask (). Они объявлены как закрытые ме-Закрытые методы
функции, реализованные без доступа к ними mask (). Они объявлены как закрытые ме-
функции, реализованные без доступа к ними mask (). Они объявлены как закрытые ме-
6’ я две вспомогательные 押曲炉金ей — byteNumber() 11MB3便
eNumber(i) возвращает номер байта, содержащего i-й бит набора. Он • ®yte ^тся целочисленным делением индекса бита i на 8, потому что каж- 口 :6а/4т состоит из 8 битов. Например, пусть i = 19. Его соответствующий "^^чный код— 00010011. Деление на 8 эквивалентно смещению двоичного 勺 а направо на 3 разряда, получаем 00000010, что соответствует 2. Таким □б азом, 19-й бит расположен во 2-м байте.
sk(i)возвращает маскированное значение байта, в котором располага- * ^^ i一近 бит. Напомним, что значение битов в байтах набора есть ‘1' для с^ановленного бита и ' 0' — в противном случае. Маскированное значение байта вычисляется по следующему алгоритму: берутся три младших би- ^ выбранного байта; оценивается их значение 明 и сдвигается влево чис- л0 1 на и разрядов. Например, пусть выбранный байт имеет содержимое 19 二 00010011. Три младших бита —это 011 = 3. Маскированное значение получается сдвигом числа 1 на 3 разряда влево, образуя значение 00001000.
Класс связанных списков
Введение
Предположим, что мы имеем совокупность элементов, число элементов которой не известно заранее или изменяется в широком диапазоне. В таком случае естественно хранить данные в связанном списке. Главное здесь то, что связанный список содержит все его элементы в цепочке. Каждый компонент цепочки хранит только одно значение и указатель. Указатель или указывает на следуюЩий элемент списка, или указывает на NULL в случае последнего элемента в цепочке.
Р“С・ 6.1. Схематическая диаграмма связанного списка.Р“С・ 6.1. Схематическая диаграмма связанного списка.
Р“С・ 6.1. Схематическая диаграмма связанного списка.
Р“С・ 6.1. Схематическая диаграмма связанного списка.
^^Возможные операции на связанном списке абстрактно выглядят следую- образом;
Insertion (вставка): добавление нового элемента в голову связанного
.списка.
.Removal (удаление): удаление элемента из головы связанного списка.
“ 8ss Сдоступ): запрос значения первого элемента списка.
Plication (дублирование): дублирование списка, когда требуется.
Size (размер): проверка, не пуст ли связанный список.
Inclusion (включение): проверка, имеется ли конкретный элемент * занном списке.1
Может показаться, что операции, доступные в структуре данныхМожет показаться, что операции, доступные в структуре данныхго списка, ограничены. Операции типа вставки илиго списка, ограничены. Операции типа вставки илисв獗固св獗固перемещения элемен,перемещения элемен,произвольное место списка отсутствуют. Позже это ограничение будет лено путем введения механизма итерации.произвольное место списка отсутствуют. Позже это ограничение будет лено путем введения механизма итерации.【та【та叩恤0叩恤0Структура данных связанного списка состоит из двух больших классов- • Класс List. Этот класс содержит открытый интерфейс структуры данИ}Структура данных связанного списка состоит из двух больших классов- • Класс List. Этот класс содержит открытый интерфейс структуры данИ}связанного списка. Интерфейс включает конструктор и все операции,связанного списка. Интерфейс включает конструктор и все операции,[ьц[ьцчисленные выше.• Класс Link. Операции на значениях списка сформированы на верху 五& го класса. Он содержит области данных, а также и связь со следук)щйм звеном.численные выше.• Класс Link. Операции на значениях списка сформированы на верху 五& го класса. Он содержит области данных, а также и связь со следук)щйм звеном.
Может показаться, что операции, доступные в структуре данных
Может показаться, что операции, доступные в структуре данных
го списка, ограничены. Операции типа вставки или
го списка, ограничены. Операции типа вставки или
св獗固
св獗固
перемещения элемен,
перемещения элемен,
произвольное место списка отсутствуют. Позже это ограничение будет лено путем введения механизма итерации.
произвольное место списка отсутствуют. Позже это ограничение будет лено путем введения механизма итерации.
【та
【та
叩恤0
叩恤0
Структура данных связанного списка состоит из двух больших классов- • Класс List. Этот класс содержит открытый интерфейс структуры данИ}
Структура данных связанного списка состоит из двух больших классов- • Класс List. Этот класс содержит открытый интерфейс структуры данИ}
связанного списка. Интерфейс включает конструктор и все операции,
связанного списка. Интерфейс включает конструктор и все операции,
[ьц
[ьц
численные выше.
• Класс Link. Операции на значениях списка сформированы на верху 五& го класса. Он содержит области данных, а также и связь со следук)щйм звеном.
численные выше.
• Класс Link. Операции на значениях списка сформированы на верху 五& го класса. Он содержит области данных, а также и связь со следук)щйм звеном.
Класс List
В этом классе объявлена структура данных связанного списка. Все интерфейсы и операции определены в классе. Однако класс содержит только один указатель на первое звено (голову списка). Значения данных списка хранятся в классе Link. Класс реализован в виде шаблона, чтобы связанный список мог содержать элементы различных типов данных, таких, как символы, строки или других абстрактных типов данных.
Поля данных:
Класс List содержит только один указатель, называемый head (голова). Он указывает на первое звено элементов экземпляров класса List.
Конструктор:
Заданный по умолчанию конструктор создает пустой список, где указатель head указывает на NULL.
Конструктор копирования дублирует список, когда это необходимо.
Деструктор удаляет данные всех элементов списка. Он вызывает метод deleteAllNodes().
Методы:
Методы, перечисленные здесь, обеспечивают необходимый интерфейс класс%
first~Node() возвращает первый элемент связанного списка.
dublicate() создает дубликат связанного списка.
is_Empty() проверяет, не пуст ли связанный список.
add_data(T value) добавляет value элемента данных в начало списка.
deleteAHNodesO освобождает область памяти, занятую списком. СлеД°" вательно, после этой операции связанный список перестает существовать
j lude(T value) проверяет, содержит ли список элемент данных и'】"' First () удаляет первый элемент данных из связанного списка.
^^^)р присваивания (=) позволяет присвоить связанный список друго- • му втеку.
друзья класса:
厂 Listiterator объявлен как друг этого класса. Listiterator предлагает 1^^^ способ обращаться и манипулировать данными элементов связанного 川 ма Все это мы обсудим в разд. 6.11.4.
СПИСКА а
Класс Link
Класс Link — вспомогательный класс. Он используется только, чтобы облег- ^^ операции класса List. Его существование не известно пользователям структуры связанного списка. Поэтому большинство функций в классе, включая конструктор, объявлено как private.
Поля данных:
Имеются два компонента данных в классе — data и next. Переменная data хранит непосредственно данные элементов списка, тогда как переменная next содержит указатель на следующий элемент списка.
Конструктор:
Конструктор Link (const Т value, Link <Т>* next) использует два параметра* Первый задает значение элемента данных, а второй есть указатель на следующий элемент.
Конструктор копирования Link(const Link<T>&) копирует поля данных как обычно.
Методы:
• Liak<T>* insert(const Т val);
Это — единственный метод в классе типа public. Он используется для вставки элемента данных после текущего элемента списка.
* ^*<Т>* dublicate () const;
н создает дубликат всей цепочки элементов.
^ья класса:
так
АМ^^ ^ассы List и Listiterator объявлены как друзья класса Link, им У"На закрытая (private) область данных класса.
Итератор списка
Этот класс обеспечивает простой способ обращения и манипулирования ком. Он включает выполнение цикла по элементам списка для удаленка。""* извольных элементов списка и добавления элементов в произвольно^ "口" внутри списка."改無
В основном идея создания итератора возникает из желания иметь м обработки каждого значения в структуре данных по очереди. Обычно процесс выполняется с использованием цикла. Однако трудность возник^ когда мы хотим иметь цикл, обеспечивающий пользователей легким доступе 5 к индивидуальным значениям, в то же время скрывая внутренние детали р© j лизации структур данных.'
С другой стороны, создавая итератор, который отделен от фактической структуры данных, мы имеем несколько преимуществ:
Функционирование итератора непосредственно зависит от структуры данных; отделение их вписывается в идеологию объектно-ориентированного программирования.
Такая реализация позволяет иметь различные стили работы итератора на структуре. Например, при обходе бинарного дерева мы можем иметь итераторы с прямым обходом узлов, с обратным и с обходом, вообще не связанным с указателями элементов этой структуры.
Наконец, такая реализация позволяет нескольким итераторам работать одновременно на одной и той же структуре данных.
Класс Listiterator — открытый потомок абстрактного базового класса Iterator. В базовом абстрактном классе описывается интерфейс абстрактного итератора. Однако реализация его операций перенесена в классы-потомки этого базового класса.
#ifndef MITERATOR.H
#define MITERATOR.H
// Iterator class
template <class T> class Iterator
public:
virtual int init() = 0;
//
Иншщгишзгщия
virtual int operator !() = 0;
//
Проверка существований
//
текущего элемента
virtual T operator ()() = 0;
//
Возвращаем текущий
//
элемент
virtual int operator ++() = 0;
//
Инкрементный оператор
virtual void operator = (const T) = 0:
//
Оператор приевайваяйя
};
#endif
В общем случае, итератор имеет следующие пять операторов: Initialization: оператор инициализирует итератор, устанавливая его в начале структуры данных.
.Termination: оператор проверяет, достиг ли итератор конца обрабатываемой итератором структуры данных.
Current Value: оператор возвращает текущее значение элемента списка, на который указывает итератор.
Increment: оператор переводит указатель итератора на следующий элемент структуры данных.
Assignment: оператор назначает новое значение текущему элементу структуры данных.
Далее мы опишем проект класса Listiterator.
Поля данных:
Класс имеет три поля данных:
Link<T>* current содержит указатель, который указывает на «текущий» элемент связанного списка.
Link<T>* previous содержит указатель, который указывает на «предыдущий» элемент, т. е. элемент который расположен непосредственно перед «текущим» элементом связанного списка.
Link<T>& list содержит ссылку на связанный список. Ссылка используется вместо указателя, потому что связанный список, обрабатываемый итератором, будучи однажды назначенным, не должен быть изменен. Ссылка °беспечивает такое ограничение. Кроме того, ссылка не может быть назна- "ена значению NULL.
Конструкторы:
Конструктор копирования List Iterator (const List It erat or <Т>&) созда- ^Ругой итератор, который обращается к тому же самому списку. ИтераторКонструктор копирования List Iterator (const List It erat or <Т>&) созда- ^Ругой итератор, который обращается к тому же самому списку. Итераторетм():етм():)*ет)*ет°」Но「о°」Но「оконструктор Liatlterator (List<T>&) в качестве аргумента берет ссылку на 'вязанный список. Он устанавливает соединение между итератором и связан- н*м списком.
Конструктор копирования List Iterator (const List It erat or <Т>&) созда- ^Ругой итератор, который обращается к тому же самому списку. Итератор
Конструктор копирования List Iterator (const List It erat or <Т>&) созда- ^Ругой итератор, который обращается к тому же самому списку. Итератор
ет
м():
ет
м():
)*ет
)*ет
°」Но「о
°」Но「о
применяться только к одному списку, но список может иметь больше итератора.
Методы:
е int init () устанавливает итератор на первый элемент в списке.
Т operator () () возвращает значение текущего элемента.
int operator ! () проверяет, достиг ли итератор конца списка или ? ния NULL.
int operator ++() переводит текущий указатель на следующий зпемен
void operator = (const Т value) присваивает текущему элементу Зн^' ние value.&
delete_Current () удаляет элемент из списка.
add_Bef ore(const Т value) добавляет новый элемент к списку перед кущим элементом.
add_Af ter (value Т const) добавляет новый элемент к списку после теку щего элемента.
Далее мы рассмотрим некоторые примеры из класса связанных списков Первый пример демонстрирует основные операторы^ имеющиеся в классе List Не совсем просто напечатать содержимое связанного списка или обратиться к произвольному элементу в списке. Однако задача становится относительно простой с использованием итератора списка. Это применение демонстрируется во втором примере, который создает связанный список с упорядочиванием элементов по возрастанию.
Пример 1
В этом примере мы демонстрируем некоторые базовые операторы связанного списка на списке чисел.
// numl.схх
#include <iostream.h>
#include "MList.h"
void mainO
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;
}
Rt<int> В(А);Rt<int> В(А);// конструктор копирования
Rt<int> В(А);
Rt<int> В(А);
d。
{ cout « "Первый элемент списка В есть " « B.first_Node() « endl;
B.delete~First();
} gle (!B.is.Empty());
cout « endl;
〃 проверка на вхождение в список int р = 3, q = 15;
cout « р << ” входит в связанный список А ? « A.is_Include(p) « endl;
cout « q << " входит в связанный список В ?
« В.is.IncludeCq) « endl;
cout « endl;
A.deleteAllNodes();
B.deleteAllNodes();
cout « "Связанный список А пуст ? " « A.is_Empty() << endl; cout « nСвязанный список В пуст ? “ << В.is_Empty() << endl;
Результат
Связанный список А пуст ? 1
элемент
списка
А
есть
0
элемент
списка
А
есть
1
элемент
списка
А
есть
2
элемент
списка
А
есть
3
элемент
списка
А
есть
4
элемент
списка
А
есть
5
элемент
списка
А
есть
6
элемент
списка
А
есть
7
Элемент
списка
А
есть
8
элемент
списка
А
есть
9
элемент
списка
В
есть
9
Элемент
списка
В
есть
8
элемент
списка
В
есть
7
элемент
списка
В
есть
6
Мрвый Мрвый 嗚^ Крвый
ыйыйый„®ыйыйыйыйыйый вввввввввв
Первыйэлемент спискаВ есть5Первыйэлемент спискаВ есть4Первыйэлемент спискаВ есть3Первыйэлемент спискаВ есть21ервыйэлемент спискаВ есть11ервыйэлемент спискаВ есть0Первыйэлемент спискаВ есть5Первыйэлемент спискаВ есть4Первыйэлемент спискаВ есть3Первыйэлемент спискаВ есть21ервыйэлемент спискаВ есть11ервыйэлемент спискаВ есть0
Первый
элемент списка
В есть
5
Первый
элемент списка
В есть
4
Первый
элемент списка
В есть
3
Первый
элемент списка
В есть
2
1ервый
элемент списка
В есть
1
1ервый
элемент списка
В есть
0
Первый
элемент списка
В есть
5
Первый
элемент списка
В есть
4
Первый
элемент списка
В есть
3
Первый
элемент списка
В есть
2
1ервый
элемент списка
В есть
1
1ервый
элемент списка
В есть
0
3 входит в связанный список А ? 1
15 входит в связанный список В ? 0
Связанный список А пуст ? 1
Связанный список В пуст ? 1
Пример 2
В этом примере мы используем итератор списка, чтобы создать сортируемый список. С использованием итератора списка обращение и управление списком становится простой задачей.
// num2.схх
^include <iostream.h>
^include "MList.h"
template <class T>
void Insert(List<T> &LL, T value)
ListIterator<T> LI(LL);
LI.init();
while (!LI && LI() < value) ++LI;
LI.add_Before(value);
roid main()
List<int> A;
Listlterator<int> P(A);
Insert(A,10);
Insert(A,1);
Insert(A,-15);
Insert(A,7);
Insert(A,12);
Insert(A,5);
Insert(A,-7);
Insert(A,0);
Insert(A,15);
(P.initO; !Р; ++Р)cout « Р() « "(P.initO; !Р; ++Р)cout « Р() « "^^^"^^^вязанныи список А есть*'《《endl;
(P.initO; !Р; ++Р)cout « Р() « "
(P.initO; !Р; ++Р)cout « Р() « "
for
р^ульнат
二=====
二данный список А есть
霑-7 0 1 5 7 10 12 15
6 ]2. Класс многочленов
L Введение
Поведение класса определено следующим образом:
.Это шаблонный класс, для которого тип данных коэффициентов многочле- вов должен быть определен пользователем.
.Создание нового экземпляра из класса Polynomial просто.
Доступны арифметические операторы типа +, -, *, /.
Доступны присвоение и формы присвоения с изменением =, +=, -=,*=, /=,片•
Доступны операторы сравнения == и !=.
Многочлены можно возводить в целочисленную степень оператором 1
Многочлен может использоваться как функция и может быть вычислен для заданного значения аргумента многочлена.
Возможны символьное дифференцирование и интегрирование многочленов.
Обеспечивается класс одночленов для символьного представления многочленов и представления полиномиальной переменной.
Класс одночленов также поддерживает операторы, определенные в классе Polynomial, чтобы обеспечить полное символьное представление многочленов.
Класс одночленов обеспечивает превращение коэффициента степени при переменной многочлена и суммы одночленов в многочлен.
Доступны операторы вывода многочленов.
Класс одночленов назван классом Polyterm. Так как многочлены суть сум- м“ полиномиальных термов, то для реализации многочленов имеет смысл ис- Пйльзовать связанные списки.
,122 Шаблон класса
^^Сс Polynomial — шаблонный класс, поэтому пользователь может выбирать Данных коэффициентов многочленов исходя из своих потребностей. На-
MQ火erMQ火er^°lynomial<Rational<int> >
MQ火er
MQ火er
использоваться для создания многочленов с рациональными коэффи-
[тами, а
Polynomial<Polynomial<double> >
может использоваться для создания многочленов от двух переменных(■ к фициентами типа double. Таким образом, шаблон класса позволяет 口电】‘虫~ вать многочлены с произвольным числом переменных.3°'
Поля данных
Переменная многочлена требуется, чтобы эффективно использовать класс 口 ременная реализована как простой одночлен с необходимыми функцион^^ ными возможностями, чтобы создавать многочлены. В классе используЮТс~ связанные списки одночленов для формирования многочленов.
Конструкторы
Имеются три способа создания многочлена:
Определяя символьную строку из переменной многочлена (с единственной переменной).
Определяя моном, который используется как символ переменной многочлена. Это — наиболее простой способ создать многочлен, так как многочлен может быть сформирован из специфицированного одночлена.
Создавая константу-многочлен из целого числа.
Имеется также конструктор копирования. Класс одночленов Polyterm также является шаблонным классом и использует те же самые шаблонные на- раметры, что и класс Polynomial, для которого он будет использоваться. В классе мы располагаем большим количеством функциональных возможностей; наиболее важными функциями класса Polyterm являются конструктор
Polyterm<T>(char *name)
(который создает одночлен с символьной строкой паше) и математические операторы. Вот некоторые примеры:
// Объявление многочлена pl с целыми коэффициентами и
// символьной переменной "х"
Polynomial<int> pl("x");
// Объявление многочлена р2 с целыми коэффициентами и
// символьной переменной “х"
Polyterm<int> х("х");
Polynomial<int> р2(х);
// Объявление одночлена х как полиномиальной переменной
// Объявление многочлена q с коэффициентами двойной точности и // символьной переменной "х" и инициализация его выражением х"2+2х+1
Polyterm<double> х("х“);
Polynomial<double> д=(хл2)+2.0*х+1;
Объявление одночлена у как полиномиальной переменной // объявление многочлена г с полиномиальными коэффициентами // символьной переменной "у*1 и инициализация его 4 выражением q(x)*(y"2+y);
term<Polynomial<double> > у(ну");
Р°г nomial<Polynomial<double> > r=q*((y"2)+y);
Операторы
В классе PolynomialВ классе Polynomialзамещаются следующие операторы:
В классе Polynomial
В классе Polynomial
==,! = , О.==,! = , О.-(унарный), +, -, *, /,力,с, =, +=, ~=, *=, /=
==,! = , О.
==,! = , О.
Прн использовании оператора следует помнить, что его старшинство ниже, нем у +, - и *. Большинство операторов имеет их обычный смысл, кроме 宀, который возводит многочлен в целую положительную степень и (), который используется для вычисления значения многочлена в точке. Большинство операторов может использоваться с классом одночленов Polyterm. Сложение и вычитание определены в Polyterm для того, чтобы создавать многочлены.
Операторы преобразования типов
Константа-многочлен может быть создана из целого числа с помощью конструктора Polynomial(int).
Методы
Класс Polynomial имеет метод tidy (void). Его единственная задача ~ удале- нпе из многочлена членов с нулевыми коэффициентами. Это экономит ресурсы памяти и делает многочлены более легкими для чтения.
Другие функции
Шаблонная функция _poly_power (Т, int) используется для возведения образ- ,Тв степень положительного целого числа. Функция Diff (Polynomial) диф- ^Ренцнрует многочлен по ее переменной. Функция Int (Polynomial) берет Определенный интеграл от многочлена по ее переменной. Постоянная интегРирования предполагается равной нулю.
氏 12.9. Потоки
^°токовый оператор вывода << сначала проверяет связанный список для мно-
"ена на пустоту и, если это так, выводит нуль. Иначе оператор выводит ^Ммй одночлен из связанного списка, используя перегруженный оператор Но;' P°lyterm, который выводит коэффициент, переменную и экспоненту од-
Пример
В качестве примера мы создаем многочлены с одной и несколькими 口^口 ными и каждый из них дифференцируем, интегрируем и возводим в квадр^^'
// pexample.cpp
#include <iostream.h>
#include "poly.h" ttinclude "rational.h"
int main(void)
{
Polyterm<double> x("x");
Polyterm<Polynomial<double> > у("у"); // одночлен с несколькими // переменными
Polynomial<double> р=(хЛ3)+2.0*(x*2)+7.0;
cout << "р(х)=” << р << endl;
cout
«
"Diff(p)="
<<
:Diff(p)
<< endl;
cont
«
"Int(p)="
<<
Int(p) <<
:endl;
cout
«
"p(x)"2="
<<
(p"2) «
endl << endl
// многочлен с несколькими переменными
/ / дифференцируем и интегрируем по у
Polynomial<Polynomial<double> > q=p+(4.0*р)*(у"2):
cout << ”q(y)=" << q << endl;
cout
<<
"Ditf(q)=" «
:Diff(q)
<< endl
cout
«
"Int(q)=" <<
Int(q) «
'endl;
cout
<<
"q(y)~2=" «
&2) «
endl;
return 0;
}
Результаты
р(х)=хЛ3+(2)хЛ2+(7)
Diff(р)=(3)хЛ2+(4)х
Int(р)=(0.25)х"4+(0.666667)x^3+(7)х
р(х)Л2=х-'6+(4)х*5+(4)хЛ4+(14)х-'3+(28)х"2+(49)
Я(у)=((4)х"3+(8)хЛ2+(28))уЛ2+(х"3+(2)х"2+(7))
Diff(q)=((8)x"3+(16)x*2+(58))y
Int (q) = ((1.33333)хЛ3+(2.66667)х"2+(9.33333))у*3+(хЛ3+(2)х"2+(7))У
q(y)"2=((16)x"6+(64)x^5+(64)x*4+(224)x^3+(448)xA2+(784))y"4
+((4)х"6+(16)х"5+(16)хЛ4+(56)х-3+(112)хЛ2+(196))уЛ2 +(хЛ6+(4)х"5+(4)хЛ4+(14)х'3+(28)хЛ2+(49))
;]3 Класс множеств
ПоведениеПоведениекласса определено следующим образом:
Поведение
Поведение
дт0 — шаблонный класс, для которого тип данных элементов конечного • множества должен быть определен пользователем.
Создание нового экземпляра множества просто.
оператор сравнения ==.оператор сравнения ==..Доступен.Доступен:Оператор присваивания = перегружен.
оператор сравнения ==.
оператор сравнения ==.
.Доступен
.Доступен
• Операторымножеств.• Операторымножеств.+ и * перегружены для объединения и пересечения конечных
• Операторымножеств.
• Операторымножеств.
.Поддерживаются операции вывода множеств.
6J3.2. Шаблон класса
Класс Set реализован как шаблонный класс для того, чтобы пользователь мог выбирать тип данных для коэффициентов полиномов и для вычислений. Например, Set<char> может использоваться для создания множества с рациональными коэффициентами в качестве элементов, a Set<string> — для создания конечного множества со строками в качестве элементов.
вЛЗ.З. Поля данных
Поля данных содержат элементы связанного списка, которым порожден класс Set.
6.134 Конструкторы
Конструктор Set (const Т) используется для создания списка элементов множества. Реализованы конструктор копирования и деструктор.
6・13・5. Операторы
В классе Set замещены следующие операторы: +, *, =, ==. Заметим, что * Weep более высокое старшинство чем +.
6・13・6. Методы
Класс Set имеет метод cardinality(), который определяет количество эле- м°Нтов в конечном множестве.
*13.7. Потоки
"ератор потокового вывода << перегружен и сначала проверяет связанный Он с°к конечного множества на пустоту и если это так, выводит《()》. Иначе
От°бражает конечное множество.
Пример
Следующий пример показывает простое использование класс; // setapp.срр
ttinclude <iostream> ttinclude <string> #include "set.h" using namespace std;
void main(void) {
Set<char> Set<char> Set<char> Set<char> Set<char>Set<char> Set<char> Set<char> Set<char> Set<char>cl, c2, c3; setA(,XD ; setB( Y); setC(Z); setD( V);
Set<char> Set<char> Set<char> Set<char> Set<char>
Set<char> Set<char> Set<char> Set<char> Set<char>
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(HhansH);
SI = setSl + setS2;
cout << ”ai << “ « al << endl;
s2 = setSl * setS2;
cout << ns2 = ” << s2 << endl;
Результаты
(X)
cl = (X,Y)
c2 = (X,Y,Z)
c3 = (X,Y)
of с2 =.3 (hans,villi)
О
библиотекаSet*библиотекаSet*стандартных шаблонов, описанная в гл. 5, также включает класс
библиотека
Set*
библиотека
Set*
6 j4・ Заключение
债 главе представлена совокупность полезных классов, реализованных в ^十+ Для каждого класса были подробно описаны определения, поля данных, онструкторы, операторы и методы и т. д.
Рреди классов, введенных в этой главе:
.Классы Verylong, Rational, Complex и Quaternion формируют основные абстрактные типы данных.
.Классы Vector, Matrix, Array, Derive, и Polynomial представляют собой верхний уровень математических структур.
• Последние (но не по значению) классы String, BitVector, List и Set — это полезные структуры данных, которые найдут применение в последующих главах.
Приложения, использующие эти классы, будут описаны в гл. 8.
Фактически имеется большое количество других классов, которые можно было бы добавить к классам, описанным выше. Например, класс многочленов со многими переменными. Другие примеры — классы алгебр Ли, конечных групп и полей Галуа и т. д.
В гл. 9 мы введем полиморфный связанный список, который способен к сохранению различных типов данных (базовых типов и абстрактных) в едином связанном списке. Этот специальный связанный список формирует основную структуру данных для языка программирования Лисп.
Глава 7
Символьный класс
Системы компьютерной алгебры, выполняющие символьную обработку, 口^陋 зали свою полезность во многих отношениях и стали необходимыми инстру ментами в научно-исследовательских вычислениях. Однако большинство д0 ступных программных систем — независимые системы, и передача математических выражений из них в иные среды программирования, такие, как С, 口饼 вольно утомительна, отнимает время и является дополнительным источником ошибок. Следовательно, полезно использовать язык высокого уровня, который предоставляет все необходимые инструментальные средства для комфортного выполнения подобных задач. Этим и займемся в данной главе.
В следующих нескольких разделах мы создадим, последовательно используя объектно-ориентированную технику, систему компьютерной алгебры SymbolicC++. Эта система может использоваться во многих областях научных исследований. Мы опишем структуры, функции и особенности реализации системы. Для демонстрации использования функций мы также включим множество примеров.
Система компьютерной алгебры строится на концепции классов в объектноориентированном программировании. Следовательно, она обладает всеми преимуществами и гибкостью объектно-ориентированного программирования. Более важные плюсы объектно-ориентированного программирования 一 модульность, возможность повторного использования кода и расширяемость. Это особенно важно для систем компьютерной алгебры, поскольку для математики характерно построение изучаемых объектов по уровням. Например, комплексные числа строятся из вещественных чисел, рациональные числа создаются из целых и т. д. С этой точки зрения математическая иерархия адекватна фи/ю^0" фии объектно-ориентированного программирования. По-видимому, это прям0 указывает на то, что объектно-ориентированное программирование явл父 естественным выбором для построения системы компьютерной алгебры. С 口Р' гой стороны, возможность повторного использования и расширяемость н门)插°’ решающую роль для хорошего кодирования, что обеспечивает большую》£口力 честь системы.“
Программы из этой главы можно найти в MSymbol.h. Этот заголовот1нЫ файл использует Mall.h и MList.h. Файл Mall.h содержит классы оснб”"" структур данных, такие, как Term, Magnitude, Function и т. п. MList.h. 一 门*' занный список структур данных, которые были описаны в гл. 6. Связанный 口川 сок используется для хранения подчиненного списка символьных перрмрн""»
Объектно-ориентированное проектирование
следующим правилам:следующим правилам:попочала определим, что представляет собой выражение. Имеется много 酬; ределених выражения. Здесь мы рассматриваем его как рекурсивную ^^'тУРУ ^анннх, которые определяются в терминах самой себя. Считая, что ЕРУ" нрнин можно использовать только знаки +, -, *, / и круглые скобки, выражена
следующим правилам:
следующим правилам:
по
по
焉 можно определять
выражение терм множительвыражение терм множитель—>■ терм [+ терм] [— терм]
выражение терм множитель
выражение терм множитель
—> множитель [* множитель] [/ множитель]
-> переменная, число или (выражение).
В ^ддратных скобках помещен необязательный элемент, а —> заменяет собой слово ото». Эти правила обычно называются порождающими правилами вы- ажения. Базируясь на этих правилах, мы сможем создать классы, которые, тесно взаимодействуя друг с другом, позволят представить математические выражения в простой и ясной форме.
Дерево выражения
В обсуждаемой здесь символьной системе выражение организовано в виде дерева. Каждый узел в дереве может представлять переменную, число, функцию или корень поддерева. Поддерево может представлять терм или выражение. Есть два типа узла — Sum и Product. Узел Sum используется для представления переменной, числа, функции или корня дерева/поддерева. Узел Product используется для представления терма, который образован произведением или частным двух элементов. Это представление соответствует порождающим правилам, упомянутым выше.
°6а класса Sum и Product являются потомками класса Symbol. Они, следо- в&^Пьно, наследуют свойства родительского класса, который описывает сим- ®°льные величины. Тем не менее все алгебраические вычисления производятся объектах из Sum и Product, которые взаимосвязаны между собой.
eyio ЛаСС Sw является более важным классом, так как он определяет символь- ® переменную, а также число как таковое. Объекты из Sum складываются ^еРемножаются между собой в процессе формирования выражения, кото- Gnp J10KB представлено в класс Sum. Все операторы, функции и интерфейсы Фун ^еЛяются в зтом же классе. В классе Sum также определены специальные & 1a^1% п°Д°бные sin(x) и cos(x)T которые будут описаны подробнее в следу- X разделах.
Составной объект
Объект а типа ” Sum" Объект Ь типа ” Sum"Объект с типа " Sum"
Элементарные корневые объекты
Рис. 7.1. Схематическая диаграмма выражения у = (а+Ь)*(а+с).
С другой стороны, класс Product описывает термы, которые перемножаются или делятся. Это ——вспомогательный класс, и его существование не известно пользователям.
Как пример, рассмотрим выражение
у = (а+Ь)*(а+с).
Объект у имеет структуру, показанную на рис. 7.1. Обратите внимание, чт0 символьные переменные а,Ь и с представлены узлами Sum. Они являются листьями (концевыми вершинами) дерева выражения. Выражение а+b составлено из других узлов Sum, которые указывают на переменные а и Ь. Выражение создано аналогично. Узел Product используется, чтобы перемножить (а+Ь) 11 (а+с), на него в свою очередь указывает другой узел Sum, чтобы сформироваТЬ дерево как выражение. Узел у типа Sum указывает на конечное выражение-
Полиморфизм дерева выражения
Вспомним, что узел Sum может содержать также переменную, число или cue"” альную функцию. Это достигается использованием наследования и поли^ физма объектно-ориентированной технологии.咧
Наследование обладает способностью создавать новые типы, импорт"»
или многократно используя описания существующих типов. Полиморф1135
вместе с динамическим связыванием позволяет функциям, доступным для базового типа, работать с производными типами. Такая функция может иметь другие реализации, которые вызываются динамически при определении производных типов во время исполнения.
В нашем случае узел Sum содержит указатель на класс, называемый Terms. У класса Terms есть два потомка — классы Magnitude и Function. В свою очередь, Magnitude имеет двух потомков — классы Variable и Number, a Function имеет множество классов-потомков. Каждый потомок класса Function соответствует специальной функции. При необходимости могут быть добавлены Другие функции (см. рис. 7.2). Используя виртуальные функции^ свойства и поведение классов-потомков можно определить во время исполнения.
Класс Terms является абстрактным базовым классом, как и его потомки, классы Magnitude и Function:
template <class Т>
class Terms
{
Public:
virtual ~Terms ();
virtual char type() const = 0;
virtual String varNameO const = 0;
} virtual void oprint(ostreamfe) const = 0;
;:тр1ат «lass T>
< ass Magnitude : public Terms<T>
Public
:irtual "Magnitude();
严У T val() const = 0;
}; Cual void set (const T) = 0;
template <class Т>
class Function : public Terms<T>
{
public:
virtual ~FunctiQn();
virtual double f(const T&) const = 0;
Методы в классах Terms, Magnitude и Function — чистые виртуальные /,^ ции. Любой класс, который объявляет или наследует чистые виртуальн * функции, является абстрактным базовым классом. Попытка создать об'е;' абстрактного базового класса вызовет ошибку во время трансляции. Слецов 丁 тельно, все чистые виртуальные функции должны быть замещены в объявляе мом классе-потомке. Абстрактный базовый класс используется для объявление интерфейса без описания всего множества реализаций для этого интерфейса Этот интерфейс определяет абстрактные операции, которые поддерживаются всеми объектами класса; он же распространяется и на классы-потомки в целях обеспечения реализации этих абстрактных операций.
Интерфейсы, объявленные классом Terms, включают методы:
е type() возвращает информацию о типе класса, независимо от того, будут ли это классы Variable, Number или классы Function разных типов.
varName() возвращает имя переменной из Variable или имя функции. Объекты из Number не имеют имени переменной.
oprint (ostreeunfe os) отправляет имя Variable, числовое значение Number или имя функции В ВЫХОДНОЙ ПОТОК OS.
В классе Magnitude объявлены следующие интерфейсы:
в val() возвращает числовое значение Variable или Number.
set (const Т num) присваивает числовое значение num значению Variable или Number.
Класс Function имеет только один дополнительный интерфейс f (const Т& а): который возвращает численное значение функции, вычисленной в а.
Рассмотрим теперь классы-потомки каждый в отдельности:
Класс Variable: как подсказывает имя, он содержит информацию。协闺" вольной переменной и соответствующее числовое значение. В классе есТЬ два поля данных паше и value. Переменная паше хранит символьное н、? Variable, a value хранит соответствующее числовое значение переменно" Конструктор читает в переменной имя и значение. Методы замещает ‘° ответствующие методы базового класса:
е type() возвращает символ ' V’,указывающий, что объект принадле^111 классу Variable.
в VarName() возвращает name, символьное имя объекта.
• oprint () отправляет паше в выходной поток.
2・
val()возвращает value, числовое значение объекта.
.set (const Т num) назначает числовую константу num в качестве значе-
ния Класс
поля field.
Number определяет шаблон для всех доступных встроенных или
пользовательских
типов данных. Например,
Number<double>
объявляет определяемый пользователем тип double, который обладает все- мм свойствами встроенного типа double. Он также обладает некоторы- мн дополнительными свойствами, которые полезны для нашей символьной системы и будут обсуждены ниже.
Класс содержит только одно поле данных
Т data .
Оно хранит значение типа Т. Его числовое значение доступно конструктору через его аргумент. Методы замещают соответствующие методы базового класса:
type () возвращает символ ? NJ, указывающий, что объект принадлежит классу Number.
е varName() возвращает NULL-строку потому, что у объекта нет определения в классе с однозначной интерпретацией.
oprint () отправляет data в выходной поток.
val() возвращает data, числовое значение объекта класса.
set (const Т num) назначает числовую константу num полю data.
Дополнительно к методам интерфейса абстрактного базового класса класс имеет два дополнительных метода:
Оператор присваивания = обеспечивает нас корректным способом присвоения значений экземплярам Number.
Оператор преобразования к типу Т — важная функция. Это — шлюз ко всем свойствам, которыми обладают данные типа Т. Он конвертирует экземпляры Number в соответствующий тип Т, когда это необходимо, тем самым позволяя использовать все функции, работающие с аргументами типа Т, с аргументами из класса Number.
I Классы-потомки класса Function описывают специальные функции ехр(х), sin(x), cos(x), sinh(x), cosh(x), sqrt(x)
Хот* многие функции могут быть включены в этот список, все они должны обладать некоторыми общими свойствами. Каждый класс содержит только °ДН。свойство Fname, которое используется для загрузки имени функции. °нц также замещают все интерфейсы, объявленные в их предках:
type() возвращает символ 3, чтобы указать, что объект есть экспо- Ненциальнгхя функция ехр(). Для индикации других функций возвращаются другие символы. Отметим, что в соответствии с соглашениями, принятыми в системе, возвращаемые символы являются символами Нилснего регистра.
VarName () возвращает поле Fname, содержащее имя функции.
oprint () отправляет Fneune в выходной поток.
f (const Т& х) возвращает числовую величину функции. Отметим , тип возвращаемого значения функции есть double, так как мы исг^^0 зуем встроенные функции из библиотеки <math.h>.""
Поля данных и типы класса Symbol
Напомним, что Sum является открытым классом-потомком класса Syrnbo] Далее, Symbol является также абстрактным базовым классом. Он сцдер^нт много чистых виртуальных функций, которые могут быть замещены в классах-потомках. Ниже мы опишем, как взаимодействуют между собой клас сы Symbol, Sum и Product в дереве выражения при формировании символьной системы.
Класс Symbol имеет четыре поля данных:
branches хранит количество ветвей, исходящих из этого узла.
Symbol<T> **ер — указатель на указатель на Symbol<T>. Эта переменная после соответствующего распределения памяти, будет содержать массив указателей на Symbol<T>. Размер массива равен branches, указат(1ли же показывают на непосредственных потомков этого узла.
Т *fac_exp — указатель на Т. После размещения в памяти массива указателей размера branches он хранит множитель для узла Sum или показателя степени для узла Product.
int *next_var — указатель на int. Он указывает на массив размера branches. Массив содержит только 0 или 1, и используется для указания наличия потомков узла с типом 'VL Эта информация необходима для деструктора класса.
Существует шесть разных типов узлов в дереве выражения. Пять из них представлены узлом Sum и один — узлом Product. При обращении к функции type() возвращаются различные символы для индикации типа узла дерева:
'S' — корень дерева выражения или поддерева. Например, а+b или а+Ъ*с. Он не имеет имени переменной.
в ‘V’ — узел Variable, например а,Ь. Обычно это лист дерева выражения. Каждый узел Variable имеет имя, связанное с ним.
'N' — узел Number, например 2.0, 4/5. Это — также лист дерева выражения.
'В'——«связанный» узел означающий, что это Variable, но он не 只日口兄°支歹 листом дерева выражения. Например, для выражения у=а+Ь, у — «связанный» узел.
в 'Р‘ ——узел Product, который используется для представления произведешь двух термов или выражений, например а*Ь. Он всегда следует за узлом Sum-
Последний тип Sum соответствует специальным функциям; при этом разный возвращаемым символам соответствуют разные функции. Например,工口' узла функции ехр(х) возвращается J еJ, для sin(x) возвращается J z1 и cos(x) возвращается 'с'. Возвращаемые символы для этих специа/1ьИь1Х функций всегда являются символами нижнего регистра.
Рассмотрим выражение
у = a*b + 1 + sin(x)
и соответствующее дерево выражения на рис. 7.3. Заметим, что в этом выражеНии использованы все шесть типов узлов: у — 'В'-тип; а, Ь, х —'『-тип; ,i,_〉N'-THri, 'sin' является 'Function'-типом, а 'Р‘ и JЭ^типы использованы в качестве промежуточных сое динительных узлов для различных целей.
Другой пример дан на рис. 7.4. Выражение имеет вид
z = 3*а"2*Ь"4 - 5 + 6*cos(x+c)
Отметим, что выражения, содержащие лишь +, -, *, / и круглые скобки, можно однозначно представить деревом выражения. Маленькие прямоугольники при узлах 'S' и 'Р‘ на диаграмме — это поля f ас_ехр, которые представляют собой множители (при узлах-потомках узла типа ,S') или показатели степени (при узлах-потомках узла типа 'Р') соответственно. Узел ‘1’ являйся специальным узлом, который мы обсудим позже. Выражение, которое следует за специальной функцией, является аргументом функции.
°* 7.4. Схематичная диаграмма z = 3*a"2*b*4 - 5 + 6*cos(x+c).
Конструкторы
У нас есть базовый класс Symbol и два его потомка — классы Sum и рГо(^^ Для символьной системы необходимо, чтобы они работали в тесном взаимо ': ствии. Так как логика программы может требовать перехода из одного клас другой в соответствии с природой структур данных, мы описываем здесь ко; структоры для всех трех классов вместе раньше, чем опишем все интерфе^с каждого класса один за другим. Мы начнем с описания роли конструктор^ базового класса. Основное назначение конструкторов заключается в иниц11али зации полей данных в классе вместе с размещением в памяти соответствующИх указателей. Этот шаг важен для обеспечения корректного функционирование классов.
Symbol(): Это — конструктор по умолчанию для класса Symbol. Поскольку Symbol — абстрактный базовый класс, то не предполагается обращения к этому конструктору для создания некоего экземпляра из этого класса в любое время. Мы включили сообщение об ошибке в тело функции. Компилятор, как правило, должен обнаруживать эту ошибку.
Symbol(int n): Этот конструктор создает узел с п ветвями. Он выделяет место в памяти для полей ер, f ас_ехр, 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, fас_ехр значением v и next_var значением w.
Деструктор просто возвращает неиспользованную память обратно в пул памяти.
Все конструкторы в классе объявлены как protected. Они видны и доступны только внутренним функциям и классам-потомкам, но не пользователям или функциям за пределами класса.
Рассмотрим конструкторы класса Sum. Данный класс имеет поле данных Terms<T>*data и является открытым потомком класса Symbol. Конструктор Sum всегда сначала вызывает конструктор класса Symbol, затем инициализирует поле data и производит некоторые другие операции:
■Цисло п. оч'личное от《°‘〃外》или «муль»,представляется простым ука-
заноем казан。заноем казан。значения п в поле f ас_ехр узла Sum в качестве множителя, как пона аимуюгцей схеме:
заноем казан。
заноем казан。
,Sum (const Т п) создает числовой узел с числовым значением, равным п, как показано выше.
.Sum(const Т n, char) создает лист (концевую вершину) типа *№ с число- внм знаненпсм, [)авньш п. Это закрытый конструктор, который используется внутри класса.
Sum (char ftype, char) создает узел типа ,Function,, соответствующий функции, заданной символом ftype в вызывающем операторе. Например, если ftype 二 1 е1, то создается узел функции ехр(). Это также закрытый конструктор.
Sum (int n, int) создает узел Sum, который имеет п ветвей.
Sum (String name,int) используется, чтобы объявить символьную переменную name.
Sum (int i,Symbol<T> **newep,T *new_fe,int *new_nx,String nm) создает узел Sum co всеми полями данных и структурами базового класса, проинициализпрованными соответствующими значениями аргументов.
Конструктор копирования дублирует узел Sum и дерево выражения, «растущего» из э того узла.
Деструктор о( в(;бождает память, которая больше не нужна, обратно в пул свободной памяти.
Конструкторы класса Product заметно проще, поскольку Product имеет юлько одно поле данных Соппп, которое является индикатором перестановоч- 1ости оператора умножения
Product (int п) создает узел Product с п ветвями.
Product (int i, Symbol<T> **newep, T *new_fe, int *new_nx) создает узел Product co структурой базового класса, проинициализированного в соответствии со спецификациями аргументов.
Конструктор копирования и деструктор для Product тривиальны.
Поле данных Comm объявлено как static. Следовательно, оно относится ко всем экземплярам класса и записано в фиксированном месте. Поскольку статическое свойство не зависит от любого конкретного экземпляра, поле Доступно в форме Product<T>: : Comm. По умолчанию мы предполагаем наШУ систему компьютерной алгебры коммутативной, и это инициализируется бедующим утверждением:
Template <class Т> int Product<T>::Соппп = 1;
口已 1 означает коммутативность системы и 0 означает обратное.
Операторы
В этом разделе мы опишем арифметические и математические ступные в системе:
。…。叫倣。…。叫倣(унарный)+, (унарный), +, -, * /, =, +=, -=, *=, /=, ==, i^ power(), df(), Int(), expand(), coeff()'
。…。叫倣
。…。叫倣
Пусть х, у — символьные переменные типа Sum и с — числовая константа
Т, тогда операторы совершают следующее:
ИлиИлих+у, х+с, с+х — складываются символьные переменные, выражения числовые константы.
Или
Или
х-у, х-с, с-х ——вычитаются символьные переменные, выражения или 勺可^ ловые константы.
х*у, х*с, с*х — умножаются символьные переменные, выражения или числовые константы.
х/у, х/с, с/х — делятся символьные переменные, выражения или числовые константы.
х=у присваивает символьную переменную/выражение у переменной х.
х=с присваивает числовую константу с переменной х.
+=, -=, *= и /= —лишь модифицированные формы оператора присваивания.
==, ! = — проверка на равенство двух выражений. Например, х+у==у+х возвращает true (истина), а х!=х возвращает false (ложь).
Результат в действительности зависит также от коммутативности оператора *. Например, a*b==b*a возвратит true, если система коммутативна, в противном случае возвращается false.
power (const Sum<T> &s, int n) возводит выражение s в степень n. Например, y=power (x,5) имеет структуру, показанную ниже.
В особом случае нулевой степени (например, y=power(х,0)) функция просто возвращает постоянную величину ' 1'.
Доложим, у вы{)ажается термами от х, тогда каждый терм у? содер- I ^щий переменную х, дифференцируется согласно стандартным пра-
вплам•
Еслн z — другая переменная, то ее производная по х есть нуль, если z не объявлена ранее как зависимая от х; в таком случае производная есть
df(?,х),
рассмотрим многочлен Р степени тг,
尸(£)二(tnXn + a?1一1:£'—' + …" + a比£' + ,…+ 02^2 + a1£ + Qo-
Так как производная одночлена а^^^ есть ка^хк~1, мы получаем, используя diff()в функции df ():
^^ = nci7l:тП~i + (几 - 1)a71一] £“一2 + "…+ ka + k£"T + …" + 2a2£ + ai . dx
Цо ходу дела мы использовали различные правила дифференцирования.
一 функция Sum<T>:: diff () манипулирует выражениями в соответствии
с правилами, перечисленными ниже:
,ZDS—一一 一一,ZDS—一一 一一=0, где с — числовая константа.
,ZDS—一一 一一
,ZDS—一一 一一
если у зависит от х, в противном случае.
Оператор дифференцирования — линейный оператор, т. е.
d , .,, & du(z) du(£)
—* u(£)+ b—b —,
axdx
где a, b — числовые константы, x — независимая переменная и u(n), и(%) — функции £.
Цепное-правило:
d # /,. df du
五五*
где / зависит от и, которое зависит от х.
Интегрирование является обращением дифференцирования / 石?(£)&:=贝£).
В Product<T>: :diff () реализуется правило дифференцирования произведения
丁(Ul(£)* &2(N)) = -3~ * U2 + "I * axaxd£
и его следствие — правило дифференцирования для одночленов
-~(а * (н(£)у1) = п * а * и^т * (也) \dx J
где а — константа.
cdx ~ с^ х^cdx ~ с^ х^±22±22
cdx ~ с^ х^
cdx ~ с^ х^
±2
2
±2
2
При дифференцировании нужно знать о наличии зависимости ^р, ду различными переменными. Такая зависимость объявляется функц * depend(). Например, y.depend(x); у.depend(z); объявляют, что у За й сит от х и z. После того как такие объявления сделаны, в операторе df (у ' должны производиться вычисления, заложенные в нем (и зависящие от да зависимости у от х); при отсутствии этих объявлений в качестве резуЛь' тата выдается 0.'
Если по какой-либо причине зависимость необходимо удалить, Мо^ но воспользоваться функцией nodepend(). Например, в нашем слуЧае у .nodepend(x) отменяет зависимость у от х (хотя при этом у остается за висимым от z).
Иногда нам нужна информация о наличии зависимости от различных переменных. Это можно проверить, используя isdependO. Например,如兄 вышеуказанных зависимостей у. isdepend(z) возвращает true, тогда как у. isdepend(x) возвращает false.
Вся информация о зависимостях между переменными хранится с использованием связанного списка MList.h. Отметим, что выбрана струк
тура связанного списка, поскольку разные символьные переменные имеют различное количество свободных переменных. И поэтому в такой ситуации трудно заранее зарезервировать необходимый объем памяти.
df(const Sum<T>& у,const Sum<T>& x,int n) вычисляет n-ю производную у ПО X.
Int (const Sum<T> &y,const Sum<T> &x) вычисляет простые интегралы от у по переменной х, пренебрегая константами интегрирования.
Рассмотрим многомлен Р степени п
尸(£)= anxn + an-ixn~ НИ %丿 4+ &2* + а】£ + а0.
Так как интеграл от одночлена a^xk есть &比£它+1/(生 + 1), мы получаем, используя integrate() в функции Int ():
飞妇頰a丿+i+•一+"/++
丿тг + 1пk + 132
В процессе вычислений мы использовали различные правила интегрирования.
—Функция Sum<T>: : integrate() манипулирует выражениями в соответствии со следующим списком правил:
2.
力 _ / / у dx^ если у зависит от £ ,“ I у * ① в противном случае.
3. Оп('1)а гор интегрирования — линейный оператор, т. е.3. Оп('1)а гор интегрирования — линейный оператор, т. е.
3. Оп('1)а гор интегрирования — линейный оператор, т. е.
3. Оп('1)а гор интегрирования — линейный оператор, т. е.
卜а * 匕⑺ + b * v{x)]dx = a * / u{x)dx + 力 * / v^dx^
Где a. I)- числовые константы, x — независимая переменная и u(x), Мб функции x.
Интсп)и1)<)вание является обратной операцией по отношению к дифференцированию:
/亲⑺血=У^-
Функция Product<Т>: : integrate() реализует правила интегрировании д.чя одночленов
а * xndx 二《;怨I In⑺а * xndx 二《;怨I In⑺п+1几次—1,
а * xndx 二《;怨
I In⑺
а * xndx 二《;怨
I In⑺
гдр а - константа.
При интегрировании иногда нужно знать о наличии зависимости между различными переменными. Такая зависимость объявляется функцией depend(). Например, у.depend(x); объявляет, что у зависит от х. После того как такое объявление сделано, Int(y,x) должен произвести вычисления, пре/^усмсугренные в нем, тогда как до объявления он выдал бы в качестве речу^иьгата у*х. На обсуждаемые здесь вопросы распространяется и описание df ().
Int (const Siw<T> &y, const Sum<T> &x, int n) вычисляет n-й интеграл у по X-
expand() разворачивает произведение выражений, что делает возможным возведение выражений в степень п. Он реализуется композицией двух важных функций - dxpandO и mxpandO . dxpandO разворачивает выражения, используя дистрибутивный закон, тогда как mxpandO использует формулу бинома Ньютона^ чтобы выполнить раскрытие бинома
(а + Ь)п =п Си。’】+п С^-^ +п 6。J%? + …• +71 Cn^ab71-1 +九 Cnbn.
Для раскрытия многочленного выражения, когда выражение является сумМой более чем двух членов, мы расширим формулу бинома, группируя член* в два множества членов. Однако нам нужно решить, как их разделить на части а и Ь. Есть два очевидных пути: разделить выражение пополам, ^тобы а и 6 были равных размеров, или же отделять один терм за один Прием. Можно показать, что в большинстве случаев последний метод бо- "ее эффективен. Другими словами, выражение h + 力2 + 力з + …• + 力化 будет Рассмотрено как сумма а + “ где а =力 и 6 =力2 + 力з + …* + 拄,工 &
(3 +力2 +力з +…+力Q"=[力1(力2 +% + ••,+人)『
"примен('1к)разложение бинома.
coef f () возвращает символьные или числовые коэффициенты выра>к、 Он перегружается в трех различных формах:
—Sum<T> coeff(const Sum<T>& s) const;
возвращает коэффициент при терме s в выражении.
— Т coeff(int) const;
возвращается постоянный терм выражения.
—Suin<T> coeff (const Sum<T>& s, int n) const; возвращает коэффициент при терме sn в выражении.
• put (const Sum<T>& si, const Sum<T>& s2) заменяет все вхождения те ма si в выражении на другой терм s2. Функция возвращает ненулевое це лое, если замена прошла успешно, в противном случае возвращается н、」%
Функция сначала проверяет каждый терм выражения. Если встречается терм si, он заменяется на s2. Это полезная функция, которая помогает упрощать выражения. Например, такое выражение из смеси sin2 х и cos2 g 10 * sin2 £ + 5 * cos2 £
можно упростить до
5 * sin2 £ + 5, заменяя все вхождения терма cos2 £ на 1 — sin2 х и объединяя подобные члены.
Пример 1
В этом примере рассматривается функция power ().
// expand.схх
#include <iostream.h>
#include "MSymbol .h11
void main()
{
Sum<int> a("a",0), b(“b\0), c(”c“,0), y, z;
у = power(7,3); у = power(a,0); у = power(a,3); cout << endl;у = power(7,3); у = power(a,0); у = power(a,3); cout << endl;cout cout coutcout cout cout« " у = " << у « endl; « " у = '* « у « endl; « п у = “ « у « endl;
у = power(7,3); у = power(a,0); у = power(a,3); cout << endl;
у = power(7,3); у = power(a,0); у = power(a,3); cout << endl;
cout cout cout
cout cout cout
у = power(a+b-c,3); cout << п у = " << у « endl; cout << endl;
у = (a+b)*(a-c); cout << " у = " « у << endl; cout « endl;
У = a+b;
}}z 二 power(у,4); cout << " z = " « z << endl;
}
}
3吧
= 343
” aY3)
@宀(3)+3*&(2)*b-3*a"(2)*c+3*a*b"(2)-6*a*b*c+3*a*c*(2)+b"(3)
У -3*ЪЛ(2)*c+3*b*c*(2)-сл(3)
у 3s a* (2) -a*c+b*a-b*c
Z 二 a*(4)+4*a'' (3)*Ь+6*ал(2)*Ъ"(2)+4*а*Ъ"(3)+Ь"(4)
Пример 2
В зтом примере мы рассматриваем дифференцирование функции
у(х)二二F — z
по переменной х. Отметим, что
поскольку система не предполагает никакой зависимости для любых двух переменных, если это не определено явным образом. Во второй части этого примера мы рассматриваем дифференцирование выражения
U®)二-V3/5 — Z//5 + 丄、'376
по переменной и. Отметим, что коэффициенты и степени переменной v — рациональные числа.
// derivatv.схх
^include <iostream.h>
^include "Rational.h"
#include "MSymbol.h"
;oid main()
int i;
Sum<int> x("x”,0), z("z",0), у;
У к 1/(1-x) + 2*x*x*x - z;
c°ut << "y = " << у << endl;
fr (i=0; i<8; i++)
у = df(у,х);
cout << "у = *' « у << endl;
cout << endl;
Sum<Rat ion al < int > > v("v",0), u;
u 二 Rational<int>(1,3) * power(v,Rational<int>(3,5))
-Rational<int>(2,7) * power(v,Rational<int>(1,5))
+ Rational<int>(1,6);
cout << "u = " << u << endl;
for (i=0; i<8; i++)
u = df(u,v);
cout << "u = " << u « endl;
Результат
у = (1-х)"(-l)+2+x*(3)-z
у = (1-х)Л(-2)+6*х-'(2)
у = 2*(1-х)*(-3)+12*х
у = 6*(1-х)"(-4)+12
у = 24*(1-х)Л(-5)
у = 120*(1-х)Л(-6)
у = 720*(1-х)"(-7)
у = 5040*(1-х)Л(-8)
у = 40320*(1-х)Л(-9)
u = l/3*v*(3/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*уЛ(-32/5)-1904256/78125*уЛ(-34/5)
u = -54286848/390625*v" (-37/5)+64744704/390625*v'' (-39/5)
Пример 3
В этом примере мы исследуем свойства оператора df (). По умолчанк две переменные считаются независимыми друг от друга. Зависимое создаваться функцией depend() и удаляться функцией nodependO.
〃 depend.схх〃 depend.сххinclude includeinclude include<iostream.h> "MSymbol.h"<iostream.h> "MSymbol.h"void main。
〃 depend.схх
〃 depend.схх
include include
include include
<iostream.h> "MSymbol.h"
<iostream.h> "MSymbol.h"
'Sum<int> a("a",0), b("b",0), u("u",0), v("v",0),x("x",0), zC'z" ,0) , y("y" ,0);
cout << "По умолчанию переменные предполагаются независимыми << endl;
cout « "df(y,x) => " « df(y,x) « endl;
cout « "у зависит от x“ у.depend(x);
<< endl;df(y,x) « endl;« "y = " « у « df(y,x) << endl;<< endl;df(y,x) « endl;« "y = " « у « df(y,x) << endl;endl;endl;cout << "u зависит от u.depend(x);cout << "df(cos(u),x)cout << "u зависит от u.depend(x);cout << "df(cos(u),x)x" << endl;=> "<< df(cos(u),x)x" << endl;=> "<< df(cos(u),x)<< endl;<< endl;cout « ”df(y,x) => " « у 二 sin(x*x+5) + x; cout cout « ”df(y,x)=> " << cout « endl;
<< endl;
df(y,x) « endl;
« "y = " « у « df(y,x) << endl;
<< endl;
df(y,x) « endl;
« "y = " « у « df(y,x) << endl;
endl;
endl;
cout << "u зависит от u.depend(x);
cout << "df(cos(u),x)
cout << "u зависит от u.depend(x);
cout << "df(cos(u),x)
x" << endl;
=> "<< df(cos(u),x)
x" << endl;
=> "<< df(cos(u),x)
<< endl;
<< endl;
и x зависит от v""<< df(cos(u),v)и x зависит от v""<< df(cos(u),v)<< endl;« endl;<< endl;« endl;cout << "u зависит от x, x.depend(v);
и x зависит от v"
"<< df(cos(u),v)
и x зависит от v"
"<< df(cos(u),v)
<< endl;
« endl;
<< endl;
« endl;
cout << "df(cos(u),v)=>
// пример
У = cos(x*x+5)-2*sin(a*z+b*x); cout << "y = " << у << endl; cout << "df(y,v) => " << df(y,v) << endl;
cout « endl;
<< endl;у.isdepend(x) у.isdepend(v) у.isdepend(z)<< endl;у.isdepend(x) у.isdepend(v) у.isdepend(z)<< endl;<< endl;« endl;<< endl;<< endl;« endl;8ut << "проверка зависимости" 8ut << "у. isdepend(x)-> " << 8ut << "y.isdepend(v)-> “ << 8ut << "y. isdepend(z)-> " << 8ut << endl;
<< endl;
у.isdepend(x) у.isdepend(v) у.isdepend(z)
<< endl;
у.isdepend(x) у.isdepend(v) у.isdepend(z)
<< endl;
<< endl;
« endl;
<< endl;
<< endl;
« endl;
Cout « "удаление зависимо ига” « endl; x*nodepend(v);
8ut « "df(y,v) => " « df(y,v) « endl;
8ut « endl;
Cout « "производная константы есть нуль" « endl;
cout « "df(5,х) => " << df(5,x) << endl; cout << endl;
// переопределение переменной у
у.clear();
у.depend(x); у.depend(z);
cout « "df(y,x)+df(у,х) => " « 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(); у.clear(); u.clear();
у.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(у,х) => О
У ЗсСВИСИТ ОТ X df(y,x) => df(у,х) у = з1п(хЛ(2)+5)+х df(у,х) => 2*x*cos(x"(2)+5)+1
и зависит от х
df(cos(u),х) => -df(u,x)*sin(u) u зависит от x, и x згсвисит от v df(cos(u),v) => -df(u,v)*sin(u) у = cos(хЛ(2)+5)-2*sin(a*z+b*x) df(y,v) => -2*x*df(x,v)*sin(x*(2)+5)-2*b*df(x,v)*cos(a*z+b*x)
проверка зависимости у.isdepend(x) -> 1 у.isdepend(v) -> 1 у.isdepend(z) -> О
удаление зависимости df(y,v) => О
вОдная константы есть нуль
(x)"f (у,х) => 2*df (y,x)
^/ *x)*df(y,x) => df(у,х)Л(2)
другой пример, 绅二 df(u,x)*sin(x) ^cTb u = 2*v*x тогда,
"=> (2*df(v,x)*x+2*v)*sin(x)
;f(y,x) => (2*df(df(v,x),x)*x+4*df(v,x))*sin(x)
+(2*df(v,x)*x+2*v)*cos(x)
Последующие примеры 4, 5 и 6 демонстрируют разные способы использова- нйЯ coef f О для получения коэффициентов выражений. Пример 7 иллюстрирует использование put () и использование тождества для упрощения.
Пример 4
// coeffl.схх
#include <iostream.h>
#include "MSymbol.h"
void main()
Sum<int> a("a",0), b(”b“,0), c(Hc",O);
cout << (2*a-3*b*a-2+c).coeff(a) << endl;
cout << (2*a-3*b*a-2+c).coeff(b*a) « endl;
cout << (2*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
f 3*a
b'(2)+c-3
Пример 5
// coeff2.схх
#include <iostream.h>
#include nMSymbol.h"
void main()
{
Sum<int> a("a",0), b(”b\0), c("cn,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 cout << "y = " << у << endl; cout « endl;
z = a;
cout
<< у.coeff(z) << endl;
z = b;
cout
<< у.coeff(z) « endl;
z = c;
cout
<< y.coeff(z) << endl;
z = a*a;
cout
<< у.coeff(z) « endl;
z = a*b;
cout
<< у.coeff(z) << endl;
z = d;
cout
<< у.coeff(z) << endl;
cout << endl;
// найдем коэффициент при терме-констг^нте
z = a;cout << у.coeff(z,0) << endl;
cout << у.coeff(0) << endl;
Результаты
у = -5*аЛ(2)*b*(2)*c+4*a-10*a*b*c"(2) +6~2*a*b+3*a*b*c+4*c*(3)*a*b_3*b*c
4-10*b*c"(2)-2*b+3*b*c+4*c*(3)*b
-10*a*c"(2)-2*a+3*a*c+4*c"(3)*a-3*c
-5*a" (2) *Ь*~ (2) +3*a*b-3*b
-5*b"(2)*c
-10*сл(2)-2+3*с+4*сл(3)
0
6~3*b*c
6
11PMp 6
//
coeff3.cxx
^include <iostream.h> 垃Delude "Rational.h" include "MSymbol.h11
void main()
{
int i;
Sum< Rational<int> > x("x"»0)^("w",0),p("p",0);
3 - Rational<int>(0);
for (i=-5; i<=5; i++)
w += Rational<int>(6+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;
}
Результаты
» = l/ll*p'(10)*x'(-5)+l/5*p-(9)*x-(-4)+l/3*p-(8)*x-(-3) +l/2*p~(7)*x~(-2)+5/7*p~(6) *x~(-l) +p~(5)+7/5*p~(4)*x+2*p~(3)*x~(2)
+3*p~(2)*x~(3)+5*p*x~(4)+ll*x~(5)
M коэффициенты суть Vll*p'(io) 1/5*р-(э) l/3p~(8) l/2*p-(7) 5"*pX6)
P'(5) 7/5*p•'⑷ 2*P'(3) 3*p-⑵ SP 11
Пример 7
// put.схх
#include <iostream.h>
#include "MSymbol.h"
void main()
{
Sum<int> a("a",0), b("b”,0), c(”c",0), w, у;
// Тест (1)
у = (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),1-power(sin(a),2));
cout << "y = " << у << endl; cout << endl;
// Тест (2)
у = sin(power(cos (a) ,2)) + b; cout « "y = " « у << endl;
у.put(cos(a)*cos(a),1-power(sin(a),2));
cout << "y = " << у << endl; cout << endl;
// Тест (3)
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 = h << у << endl;
Результаты
у = аЛ(2)*b+a*sin(cos(a)'(2))*b+b"(2)*a+b"(2)*sin(cos(а)Л(2))
у = 2*a*(2)*c+2*a*sin(cos(a)"(2))*c
+4*c"(2)*a+4*c"(2)*sin(cos(а)Л(2))
у = 2*a*(2)*c+2*a*sin(l-sin(a)Л(2))*c
+4*c*(2)*a+4*c"(2)*sin(l-sin(a)*(2))
у = sin(cos(a)*(2))+b
у = sin(l-sin(a)*(2))+b
у = 2*a*(3)*df(b,c)+4*a*(2)*b*df(a,c)+a*b*c
у = 2*cos(с)*(3)*df(b,c)-4*cos(с)"(2)*b*sin(c)+cos(c)*b*c
у = 2*cos(c)*(3)*exp(a)-4*cos(c)Л(2)*b*sin(c)+cos(c)*b*c
том примере мы интегрируем выражение
贝①)=(1 — £)2 + COS(£)+ Х6Х + С + N
переменной х. Мы объявляем z как зависимую от х. Так как система не
2T никакой зависимости любых двух переменных, мы можем ис- с как символьную константу. Мы также используем2T никакой зависимости любых двух переменных, мы можем ис- с как символьную константу. Мы также используем>лполагает никакой зависимости любых двух переменных, мы можем ис- зовать с как символьную константу. Мы также используем
2T никакой зависимости любых двух переменных, мы можем ис- с как символьную константу. Мы также используем
2T никакой зависимости любых двух переменных, мы можем ис- с как символьную константу. Мы также используем
хех = хег — / exdx.
〃 integ.cpp
/include <iostream.h>
#include "MSymbol.h"
#include "rational.h"
void main()
{
int i;
Sum<Rat ional < int > > x(”x\O),c(”c",0), z(Hz" ,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;
}
『out « endl;
Результаты
aa^=s=====
У ° l'2*x+x"(2)+cos(x)+x*exp(x)+c+z
У ' x~x"(2)+1/3*хл(3)+sin(x)+x*exp(x)-exp(x)+c*x+Int(z,x)
У - l/2*x"(2)-l/3*x"(3)+l/12*x"(4)'cos(x)+x*exp(x)-2*exp(x) +l/2*c*x"(2)+Int(Int(z,x),x)
У ~ l/6*x"(3)-1/12*хл(4)+1/60*хл(5)-sin(x)+x*exp(x)-3*exp(x) +l/6*c*x"(3)+Int(Int(Int(z,x),x),x)
7・5・ Функции
Мы описали пригодность для нашей символьной системы специальных ф ций на примере синуса и косинуса. Все доступные операторы могут 叩口乂^'"" ся с этими функциями, например оператор дифференцирования df ().‘卜、
Функция записывается в дереве выражения как лист, т. е. у нее нет по ков. Однако функция должна оперировать с аргументами. Нам нужно подходящее место для хранения аргумента. Поскольку аргумент может би ” затребован любой функцией, он также должен принадлежать дереву внра: ния. Мы решили сделать его, подобно дереву выражения, коренящимся самой функции. Рассмотрим функцию sin(x+y) и структуру ее дерева рис. 7.5.
Рис. 7.5. Схематичная диаграмма функции sin(x+y).
На диаграмме узел 'z‘ обозначает функцию sin() вместе с ее данными в классе sin. Ее аргумент, указываемый указателем ерт является корнем дерева выражения аргумента. В таком пр ед ставлен ии мы успешно реализуем функцию как часть дерева выражения. Ниже мы укажем функции, доступные в символьной системе и операциях, связанных с ними. Пока в классе реализовано только семь специальных функций:
ехр(), sinh(), cosh(),sin(), cos() , sqrt(), ln()
Методы, которые связаны с ними, включают:
• Правила дифференцирования:
/、 九 /、d . .du .d .du.,ехр(м) = — exp(u),— sinhu =—coshu,— coshu =—smhu,axaxaxaxax/、 九 /、d . .du .d .du.,ехр(м) = — exp(u),— sinhu =—coshu,— coshu =—smhu,axaxaxaxaxduddusin u — — cos — cos и =———sm % axaxaxduddusin u — — cos — cos и =———sm % axaxaxdxdxdu 1 d —史1dx 2,S,dx nU 彰 нdu 1 d —史1dx 2,S,dx nU 彰 н
/、 九 /、d . .du .d .du.,
ехр(м) = — exp(u),— sinhu =—coshu,— coshu =—smhu,
axaxaxaxax
/、 九 /、d . .du .d .du.,
ехр(м) = — exp(u),— sinhu =—coshu,— coshu =—smhu,
axaxaxaxax
duddu
sin u — — cos — cos и =———sm % axaxax
duddu
sin u — — cos — cos и =———sm % axaxax
dx
dx
du 1 d —史1
dx 2,S,dx nU 彰 н
du 1 d —史1
dx 2,S,dx nU 彰 н
7 6 Упрощение выражений
мне играет существенную роль в символьной системе. Оно переводит У"Р表енне в эквивалентную, но более простую форму. Ниже приведены неко- в呪【с примеры упрощения алгебраических выражений:
2+5 3*х*2*х и+02+5 3*х*2*х и+0-> 7
2+5 3*х*2*х и+0
2+5 3*х*2*х и+0
-> х
-> и
sin(2*x-x-x) -> О
где символ -> означает «упрощает до» или «превращает в». Рассмотрим другое выражение,
8*а*а + b*c* (а + b - а - 2*Ь + Ь) + 2 - power(а,2)*3 + cos(х - х)
Здесь имеется явная усложненность. Применив алгебраические упрощения, можн° получить более «чистый» и «пригодный» результат; например, упрощенная форма вышеприведенного выражения есть
5*а"2 + 3
Упрощение выражения базируется на существующих правилах упрощения для каждого типа математического выражения. Например, упрощение вышеприведенного выражения включает следующие алгебраические манипуляции:
8*а*а +> 8*аЛ2 +> 5*а*2 +> 5*а^2 +8*а*а +> 8*аЛ2 +> 5*а*2 +> 5*а^2 +b*c*(а + b - а - 2*Ь + Ь) + 2 Ь*с*0 + 2 - 3*а"2 + cos(0) 0+2+1 3b*c*(а + b - а - 2*Ь + Ь) + 2 Ь*с*0 + 2 - 3*а"2 + cos(0) 0+2+1 3- power(a,2)*3 + cos(x - х)- power(a,2)*3 + cos(x - х)
8*а*а +
> 8*аЛ2 +
> 5*а*2 +
> 5*а^2 +
8*а*а +
> 8*аЛ2 +
> 5*а*2 +
> 5*а^2 +
b*c*(а + b - а - 2*Ь + Ь) + 2 Ь*с*0 + 2 - 3*а"2 + cos(0) 0+2+1 3
b*c*(а + b - а - 2*Ь + Ь) + 2 Ь*с*0 + 2 - 3*а"2 + cos(0) 0+2+1 3
- power(a,2)*3 + cos(x - х)
- power(a,2)*3 + cos(x - х)
Фактически, не существует конечного набора правил для упрощения всех типов алгебраических выражений. Это означает, что трудно описать алгоритм, который может полностью упростить произвольное алгебраическое выражение. Тем не менее на практике могут быть достигнуты очень хорошие результаты с подходящим конечным набором правил упрощения.
7・6,1・ Канонические формы
Математические выражения могут существовать в различных эквивалентных символьных представлениях. Каноническая форма есть определенный «стан- 期рт》представления для таких выражений. Например, многочлены обычно пРедставляются серией членов с убывающими степенями, каждому члену пред- ^ествует его числовой коэффициент. Ниже показано преобразование, которое соответствует соглашению «стандарта»:
-5*e3+e6*8 + 4 - 2*e2 T8*e6-5*e3一2*№+4.
преобразование в каноническую форму может увеличить или уменьшить 5°*HocTb выражения или сводится лишь к перегруппировке термов. Как пра- 如”0,такое преобразование приводит к улучшению удобочитаемости. Тем не Менее наиболее важным применением является определение «эквивалентно- СТ"》двух различных символьных представлений. Следовательно, нам нужны
некоторые правила эквивалентных преобразований из одного представл(ч
другое. Получить исчерпывающий список корректных правил, однако,
трудно.
Когда каноническая форма выражения тщательно определена, преобр^. вание в каноническую форму двух эквивалентных выражений приведет к 口 沁 тичным, или почти идентичным представлениям. Это существенно уменьщ- И' сложность при сравнении выражений.淚‘
■тся■тсяВ нашей символьной системе математическое выражение представляе^
■тся
■тся
древовидной структурой. Процесс упрощения состоит из двух этапов: первые есть преобразование в каноническую форму, а другой состоит в приведении более простой форме, базирующейся на некоторых известных правилах На пример, а*(-1) может быть переписано как -а. Символьное упрощение проис ходит следующим образом:
а*(-1)
[Шаг 1] -> -(а*(1)) [Шаг 2] -> -(а*1) [Шаг 3] -> -(а) [Шаг 4] -> -а
Процесс упрощения включает три шага канонических преобразований (шаги 1, 2, 4) и один шаг, основанный на правилах упрощения (шаг 3) (см. рис. 7.6).
Рис. 7.6. Схематичная диаграмма процесса упрощения для а*(-1).Рис. 7.6. Схематичная диаграмма процесса упрощения для а*(-1).
Рис. 7.6. Схематичная диаграмма процесса упрощения для а*(-1).
Рис. 7.6. Схематичная диаграмма процесса упрощения для а*(-1).
9Toт пример иллюстрирует, что каноническая форма является способом оличения множества допустимых форм выражения. Следовательно, прави- о'Р прошения нужны только для ограничения класса выражений.
№ У' у
,6 2・ Пра®ила упрощения и методы
м разделе мы опишем более детально правила упрощения и преобразо- ^ е в канонические формы. Пусть е,el,е2,еЗ,е4— произвольные алгебрам- »ескне выражения, а с, cl, с2 обозначают положительные числовые констан-
ты-ты-7.7.7.7.Е& рНС*Е& рНС*I Некоторые примеры правил упрощения для каждого оператора помещены
ты-
ты-
7.7.
7.7.
Е& рНС*
Е& рНС*
Операции
Функции
Cl + С2 с
Sum::shrink_4()
е + 0 Т е
Sum::shrink3()
Cl * С2с
Sum::shrink_l()
0 * е т 0
Product::shrink_3()
1 * е т е
Product: :shrink_l ()
0。т 0
Product: :shrink_3()
Iе т 1
Product ::shrinkJ()
е°^1
Product::gather()
Рис. 7.7. Правила упрощения.
Оператор вычитания (-) имеет свойства, аналогичные оператору сложения (+), а оператор деления (/) имеет свойства, аналогичные оператору умножения (*). Правила преобразования в канонические формы даются на рис. 7.8.
Операции
Функции
(ci * ei) *(C2 * 62)T c * (ei * 62), где c = ci * c2 ((ei + 62)+ e3) + e4 ei + 62 + e3 + e4 ((ei * ©2)* &3)* ©4 —> 61 * &2 * &3 * &4 ei + (c・2)ei + e2
Sum::shrinkJ() Sum::shrink_2() Product ::shrink_2() Sum::shrmk_5()
Р“с. 7.8. Канонические правила преобразования.
Наконец, отдельные члены группируются, суммируются или умножаются, е^н они принадлежат одному и тому же классу. Например,
2*а + 3*а->5*а
exP(z) + exp(z) -> 2*exp(z)
PX-2) * p'(5) -> pX3)
Cos(x) * cos(x) -> (cos(x))"2
:в итоге получаем упрощенное выражение. Может вызвать удивление огра- "енность нашего набора правил упрощения. Разве нельзя упростить выра- Ние к самой простой возможной форме? Ответ на этот вопрос не очевиден.
Тем не менее преобразование к канонической форме играет важную 口 процессе упрощения, поскольку оно уменьшает количество основных 口 "卜 в упрощения и сложность процесса упрощения.в^лТем не менее преобразование к канонической форме играет важную 口 процессе упрощения, поскольку оно уменьшает количество основных 口 "卜 в упрощения и сложность процесса упрощения.в^лВ нашей канонической форме для выражений произведения сВ нашей канонической форме для выражений произведения соперандами только первый операнд может быть числовым. Это заменыоперандами только первый операнд может быть числовым. Это заменывлечетвлечетЧ11С」ОВЬпЧ11С」ОВЬп[Ми[МипРав«лопРав«ло
Тем не менее преобразование к канонической форме играет важную 口 процессе упрощения, поскольку оно уменьшает количество основных 口 "卜 в упрощения и сложность процесса упрощения.в^л
Тем не менее преобразование к канонической форме играет важную 口 процессе упрощения, поскольку оно уменьшает количество основных 口 "卜 в упрощения и сложность процесса упрощения.в^л
В нашей канонической форме для выражений произведения с
В нашей канонической форме для выражений произведения с
операндами только первый операнд может быть числовым. Это замены
операндами только первый операнд может быть числовым. Это замены
влечет
влечет
Ч11С」ОВЬп
Ч11С」ОВЬп
[Ми
[Ми
пРав«ло
пРав«ло
(е * с) т (с * е),
которое является применением закона коммутативности умножения. Следов
тельно, имеет место следующая эквивалентность, и она не показана на рис 7;
е * 1 ^~> 1 * ее * 0 <~> 0 * е,е * с <—> с * е.
Аналогично, коммутативность сложения влечет правило замены:
(е + с) -> (с 4- е).
Это устраняет многие необязательные правила сложения. Другим важным правилом преобразования к канонической форме является правило переноса умножения за скобки. Например,
(5 * q) * (с2 * е2) ^~>(Q * ej * (с2 * е)
(ci * ei)/(c2 * е2) <~> (ci/c2) * (勺/%)・
Эти правила непосредственно не вызывают упрощения, но преобразую^' выражения в канонические формы, которые в дальнейшем могут быть упрощены.
Коммутативность
Итак, мы предполагаем справедливость коммутативности умножения символов. Тем не менее в некоторых приложениях это не так. Для примера в общем случае
А * В ^ В ^ Ау
если А. В — матрицы. Это заставляет нас подумать о потребности в неком” мутативном операторе. В действительности имеется большая ветвь математики (называемая «алгебры Ли»), в которой рассматриваются некоммутативные операторы. Следовательно, мы должны уметь строить в нашей символьной системе некоммутативные операторы.
В нашей системе компьютерной алгебры коммутативность предполагается по умолчанию. Для того чтобы определить некоммутативную алгебру, мы 工心 пользуем функцию Commutative(T,int t). Первый аргумент определяет т"" данных основной области, с которыми мы имеем дело. Это —тип, опред<'^еН, ный в процессе объявления переменных. Второй аргумент t определяет выбор коммутативности. Если t = 0, некоммутативность имеет место. В протнвн。" случае, имеет место коммутативность.
рассмотримрассмотрим////coffiinute • сххcoffiinute • сххс」(*дук)щую програм му:с」(*дук)щую програм му:include#includeinclude#include<iostream.h> "MSymbol.h"<iostream.h> "MSymbol.h"
рассмотрим
рассмотрим
//
//
coffiinute • схх
coffiinute • схх
с」(*дук)щую програм му:
с」(*дук)щую програм му:
include
#include
include
#include
<iostream.h> "MSymbol.h"
<iostream.h> "MSymbol.h"
void main。
{ Sum<int> a("a",0), b("b",0), y;
cout << "коммутативная алгебра" << endl; cout « "==================" « endl;
I/ Система коммутативна по умолчанию
у =: a*b*a; cout << *' у = “ << у << endl;
у = a*b-b*a; cout << " у = ” << у « endl;
« у << endl;« у << endl;у = power(a-b,2); cout << " у = cout << endl;
« у << endl;
« у << 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;
Commutat ive(int(),1);
cout << 11 у = " << у << endl;
Результат
к°Ммутативная алгебра
у = а,2)*ь
У = о
У 工 a^(2)-2*a*b+b"(2)
некоммутативная алгебра
у = а*Ь*а у = a*b-b*a у = a"(2)-a*b-b*a+b~(2)
коммутативная алгебра
у = а*(2)-2*а*Ь+ЬЛ(2)
В приведенном примере обращаем внимание на то, как можно вводИт функцию в любое время в пределах программы для определения или н3ML нения свойства коммутативности. Последние два отображенных утвержден; показывают, что при переключении режима с некоммутативного на коммута тивный выражение автоматически перевычисляется. Это правило не примени мо при обратном переключении режима.
Символьный и числовой интерфейсы
Когда мы оперируем с символьным алгебраическим выражением, нам также бывает нужно его числовое значение. Наша система справляется с этой задачей следующим образом:
Вначале получаются алгебраические значения всех переменных и операторов, входящих в состав выражения, путем подстановки значений аргументов, которые последний раз им назначались (если значение никогда не назначалось, в качестве такового берется сама переменная).
Вычисляется выражение при помощи стандартных алгебраических правил.
В состав класса включены три функции для определения числовых значений и вычисления алгебраических значений выражений. Пусть х, у, z- символьные переменные, у присвоено алгебраическое выражение со свободной переменной х, a z присвоено значение числовой постоянной. Тогд^
х. set( const Т num) присваивает переменной х числовое значение постоянной num.
у.value() возвращает вычисленное алгебраическое значение выражения у.
z.nvalueO возвращает числовое значение переменной z.
Отметим, что присвоение числовой константы переменной с помощью ^Ie■■ тода x.set(num) отличается от использования оператора присваивания х二ng Метод х. set() просто создает дубликат числовой переменной х. Эта величина может быть изменена, и при этом символьные свойства переменной х оста^тс" неизменными. После же выполнения оператора присваивания x=num перемен” ная х становится числом num. Переменная х теперь семантически эквивалентна num, т.е. х теряет свои символьные свойства (и приобретает символьные св°"‘ ства num) в выражении, и они не могут быть восстановлены. Эти разлт"' демонстрируются в следующей программе:
炉elude include
<iostream.h> "MSymbol.h"
void gin。
Sum<double> x("x",0), y("y",0), z("z",0);
double cl = 0.5, c2 = 1.2;
У =: x*x + z/2. °;
cout « "У = " « У « endl;
cout << "Величина у есть " << у.value() << endl; cout « endl;
x.set(cl); z.set(c2);
cout « "Положим x = " « cl << ", z = " « c2 << endl; cout << "Величина у = " << у << " есть " << у.value() << cout « endl;
cout << "Заменим x на " << cl << " и z на " << c2
<< ",тогда" << endl;
X = cl;
z = c2;
cout << "y = *' << у << endl; cout << endl;
x.clear() ; z. clearO ;
У = x*x*z + 0.7*z - x*z;
cout « "y = " << у << endl;
x.set(cl);
cout << "Положим x = " << cl << endl;
cout << "Величина у есть " << у.value() << endl;
cout << endl;
z-set(c2);
cout << "Положим z = " << c2 << endl;
'out << "Величина у есть " << у.value() << endl;
%ульнан
「 x~(2)+0.5*z
еЛ11Чина у есть x"(2)+0.5*z
Положим х=0.5, иг=1.2
Величина у = х~(2)+0.5*z is 0.85
Заменим х на 0.5 и z на 1.2, тогда
у = 0.85
у = х"(2)*z+0.7*z-x*z
Положим х = 0.5
Величина у есть 0.45*z
Положим z = 1.2
Величина у есть 0.54
7・9. Заключение
В этой главе введена новая система компьютерной алгебры, которая строится с использованием объектно-ориентированного программирования. Мы отметили параллелизм иерархии математических структур и философии объектноориентированного программирования, что служит мотивацией использования языка C++ для этой разработки. В этой главе мы описали следующее:
Объектно-ориентированная разработка: мы объяснили, как математические выражения могут быть определены с помощью правил построения и как это связано с нашей символьной системой.
Поля данных и типы символов-, мы очертили поля данных, описанных в классах, и основные типы символов, используемых в системе.
Операторы: были описаны назначение и функциональные свойства арифметических операторов и функций. Дано описание дифференцирования и интегрирования.
Функции: были описаны специальные математические функции, такие, как синус, косинус и др.
Упрощение: это важная часть системы компьютерной алгебры, посредством которой сложные выражения приводятся к более простым формам.
Коммутативность: мы описали, как коммутативность играет важную роль в системе.
Символьный п числовой интерфейс: система компьютерной алгебры не "°" на без интерфейса между символьным и числовым аспектами. Здесь 6曲" описаны правила и методы, которые обеспечивают такой интерфейс.
В следующей главе мы применим классы, реализованные в гл. 6 и к приложениям в различных областях, таких, как, например, теория чисел, тическая динамика, сферические гармоники, кривизна и др.
Приложения
функциональное назначение системы компьютерной алгебры лучше всего иллюстрируется приложениями. В этой главе мы представим приложения, связанные с классами, введенными в гл. б и 7. Представленные здесь задачи име- ххг отношение к теории чисел, нелинейной динамике, специальным функциям в математике и физике и др. В целом приложения распределены по классам. В каждом из них сначала описывается математический контекст, потом следует предлагаемое решение задачи и затем приводится фрагмент программы, которая решает задачу в символьном виде. В этой главе представлено двадцать восемь приложений:
Простые числа
Большие простые числа
Обратное отображение и счетное множество
Геделева нумерация
Логистическое отображение
Теорема о сжимающем отображении
Блуждающие решения
Системы итерационных функций
Логистическое отображение и показатель Ляпунова
(1。)Множество Мандельброта
Многочлены
Вычисление кумулянтов
Внешнее произведение
Первые интегралы
Сферические гармоники
,6) Небесная механика
,丄7) Разложение в ряд Тейлора дифференциальных уравнений
〉丄&) Коммутатор двух векторных полей
Производная Ли и свертка векторного поля
U) Норма Гильберта—Шмидта
::丄)Свободная пара и гамильтонова система Аппроксимация Паде
;')Псевдосферические поверхности и солитонные уравнения
4)Метод Пикара
Техника рядов Ли
Спектр малых групп спинов
Системы и нелинейные отображения с хаотичным поведением
Численно-символьное приложение
8Л. Класс BitVector
8.1.1. Простые числа
В этом разделе мы применим класс BitVector к теории чисел. Простое ^Uc до —это положительное целое р〉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.ii"-
// Prime.h
#ifndef PRIME_H
#define PRIME_H
#include <assert.h>
#include <iostream.h>
#include "Bitvec.h"
class Prime
private:
//
皿
0оля данных
signed int max_num, max_index, index, p, q, current
BitVector
bvec;
miblic:
P 〃 Конструкторы
prime。;
prime(unsigned int num);
gid reset (unsigned int);
int step。;
void run();
int is.prime(unsigned int) const;
^signed int current_prime() const;
unsigned int operator () (unsigned int) const; };
Prime::Prime()
:шгех.пиш(О) , max_index(0) , index (0), p(0), q(。), current(0), bvec() {}
Prime: :Prime(unsigned int num)
:max_num(mun) , max_index ((num+1) /2 - 1) , index (0), p(3), q(3), current(2), bvec(max_index, OxFF) {}
void Prime::reset(unsigned int num)
assert (num > 1);
max_num = пшп;
max,index = (num+l)/2 - 1;
index = 0; p = 3; q = 3; current = 2; bvec.reset(max^index, OxFF);
int Prime::; {
if (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;
if (q < max_index)
// удаляем все нечетные кратные р, начиная с р^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, to 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 (!(num%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
N пуктор Prime (insigned int N) определяет верхний предел N последо- «0НС*остн простых чисел {.}, где 1 < р^ < ^, обеспечивает соответствую- в&’е битовый вектор и включает все его биты.
М'опо水Им, что р является элементом класса Prime; тогда имеющиеся шесть ов работают следующим образом:
.「С отыскивает следующее простое число, вычеркивая все кратные те- • step、
сущего простого числа, начиная с £
run() повторно выполняет метод step() до тех пор, пока все простые, мень* шие М не будут найдены.
reset (int М) переопределяет верхний предел последовательности простых чисел.
.is prime(int num) проверяет, является ли num простым числом. Он рабо- .j^er в предположении, что соответствующая битовая таблица уже создана. .current _pr ime () возвращает текущее простое число, найденное итерациями метода step().
• Оператор взятия индекса p(int М) возвращает М-е простое число последовательности.
Посмотрим теперь на простую программу, которая вычисляет общее количество простых чисел до 100,1000, 10000,100000,1000000 и отображает первые 20 простых чисел. Она также демонстрирует возможность доступа к г-му простому числу.
// sprime.схх
tinGude <iostream.h>
^include "Prime .h"
void main()
unsigned int i, j, maxO = 1000000, count = 0;
Prime p(max0); // задаем верхний предел последовательности
P-run(); // генерируем последовательность простых чисел
// для всех нечетных чисел > 3
〃 проверяем, просты ли они
for (i=3, j=100; i<=max0; i+=2) {
if (p.is_prime(i)) count++;
// количество простых меньших 100, 1000, ..., 1000000
if (i == j-1)
j *= 10;
cout « ” Имеется " « count + 1
<< "простых чисел до " << i+1 << endl;
cout « endl;
// печать первых 20 простых чисел for (i=0; i<20; i++) cout << p(i) << cout << endl; cout « endl;
// случайная выборка нескольких простых чисел
cout << ”100-е простое число есть *' << р(99) << endl;
cout << н200-е простое число есть ” « р(199) << endl;
cout << "3000-е простое число есть " << р(2999) << endl;
cout << "10000-е простое число есть ” << р(9999) << endl;
cout << "12500-е простое число есть " << р(12499) << 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. Класс Verylong
Большие простые числа
В этом разделе мы используем класс Verylong, чтобы проверить, является 口" большое положительное целое число простым числом. Простое число — э,г° положительное целое р〉1, такое, что никакое другое целое не делит р, кро£° 1 и
Функция is_prime(unsigned int num), которую мы описали в разд. 8」工 может использоваться для проверки простоты числа num. Она работает при условии, что соответствующая битовая таблица уже создана. Для больший чисел (больше 232) встроенный тип unsigned int слишком тесен. Для преа1°~
эт0Г0 ограничения могут быть использованы числа из Verylong. Однако ^еН%ая проблема этого гтгоритма — огромные затраты памяти для битовой °с"° tri Для больших чисел, например 20-значных, это становится совершен-
д^реэльным.
н° мн опишем другой алгоритм проверки на простоту положительного целого со скромными запросами к памяти. Это достаточно наглядный алгоритм “"оверкв на простоту. Для данного положительного целого N мы делим N "Тиедовательно на 〃 = 2,3,5,… до наименьшего в для которого TV mod р = 0. пи мы дойдем до такого р, то ^ — не простое число и имеет множитель р.
Если же на каком-то шаге мы обнаружим, что Nmodp В 0 и [N/p] < р, мы заключаем, что N — простое число.
Рассмотрим это рассуждение подробнее.
Данное положительное целое N мы делим на последовательность «пробных делителей»
2 = do < di < d2 < d3 < ■ • • < 厶,
которая включает все простые числа < \/N (последовательность может также включать некоторые составные числа, если это удобно). Последний делитель в последовательности d? — минимальное число > y/N. Если на каком-либо шаге ^ mod(Ц = 0 для 0 0 5 0% мы заключаем, что N составное. В противном случае оно — простое число.
Последовательность пробных делителей do, di, d?, • • • d^ работает наилучшим образом, когда она содержит только простые числа. Иногда удобно включить также некоторые составные числа, поскольку такую последовательность обычно легче сгенерировать. Одна хорошая последовательность может быть сгенерирована с помощью следующего рекуррентного соотношения:
00 = 2,小=3,d2 - 5,
dk 二 dk-i +2 для нечетных к > 2,
dk 二"k—i +4 для четных к〉2.
Вот несколько первых пробных делителей, сгенерированных этими соотношениями:
2 3 5 7 11 13 17 19 23 25 29 31 35 37 41 43 . •..
9TH последовательность не содержит кратных 2 или 3 и первые 9 чисел — ВСе простые. Тем не менее она также включает некоторые составные числа, напрнмер 25, 35, 49 и др.
〃 isprime.срр
"include <iostream.h>
*include "Verylong.h"
;empiate <class T> int is~prime(T p)
T j(2), zero(0), one(l), two(2), four(4);
Т limit = T(sqrt(р)) + one;
if (j < limit && p%j == zero) return 0;
if (j < limit && pZj == zero) return 0;
j += two;
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 << "Введите положительное целое число:";
сin >> х;
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], которые выполняют проверку за приемлемое время.
叩0‘叩0‘ереСНым расширением была бы программа поиска всех пар простых “ близнецов, например 21, 23. Вот простенький вопрос: почему нет других чИ^^ триплетов (т. е. 3-х подряд идущих простых чисел), кроме 2, 3, 5? 叩% иа 2" — 1 (% С N) называются числами Мерсенна. Для к = 2, 3, 4, 7, 13, :;61, 89, 107, 127, 521, 607, 1279, 2203, 2281, 3217, 4253, 4423, 9689, 9941, I。' 14’' мы получаем простые числа. Большие простые числа также играют
叩0‘
叩0‘
“ ;ю роль в шифровании данных и сетевой безопасности.
в ^1рОСТОе число 2比一1 (к = 11213) можно найти, используя следующую не- ль ШУ10 программу. Это — большое число, состоящее из 2816 цифр.
〃 Mersenne.схх
include <iostream.h>
#include ''Verylong.h'*
int main()
Verylong p, one('*l"),
mersenne("11213");
p = pow(two,mersenne) - one; cout << p << endl;
return 0;
822・ Обратное отображение и счетное множество
Множество N х N счетно^ потому что оно равномощно множеству натураль- ных чисел N. Другими словами, существует взаимно-однозначное соответствие ме*ду N и N х N.
Напишем элементы N х N в виде массива, как показано ниже:
(1,1)
1 >
/0,2)
/,3)
/(1,4)
/(1,5)
>
乙2,5)
夕3,3)(
4л)
(3,5)
(4,3)
(4,4)
(4,5)
(5,1)
(5,2)
(5,3)
(5,4)
(5,5)
На схеме стрелками показан способ упорядочивания элементов N 乂 卜 линейную последовательность, а именноN
(1,1), (2,1), (1,2), (3,1), (2,2), (1,3), (4,1), (3,2),...
между nмежду nТаким образом, существует взаимно-однозначное соответствие N х N. Если (m, n) € N х N, тогда имеем
между n
между n
/(т,几)=^(т + п ~ l)(m + п — 2) + 儿
Соотношение (8.1) может быть получено следующим образом. Пара (т,7» н ходится в (та + 几一 1)-й диагональной полосе в вышеуказанной таблице, и Оц' есть n-я пара, отсчитывая от левого края полосы. Первая полоса содержу только одну пару. Во второй имеется две пары и т. д. Таким образом, в ^己 шей счетной процедуре (т,几)располагается в позиции, задаваемой выра>ке нием
几 + ,2
比=1
Отсюда сразу следует (8.1). Решим теперь обратную задачу, т. е. зная номер пары /(т, п), найдем ее компоненты тип.
Для того чтобы найти обратное отображение, мы сначала должны определить, на какой диагональной полосе находится элемент с номером f(m,n). Отметим, что m = 1 соответствует последнему элементу на диагональной полосе. Пусть к = т + п — 1, таким образом, для тп = 1, к = 1 + п ~ 1 = п. Согласно (8.1),
f г 、1 /1\冗2 + 几 卜2 + k
/(!>«) = -(n)(-l + n) + n =—-—.
Последний элемент к-й диагонали имеет номер
к2 + к
тогда как последний элемент (к — 1)-й диагонали имеет номер 、(к-1)2 + (к-1) к2_к
〃gn)== -•
Таким образом, если /(m, п) лежит на k-й диагонали, то
к2 - к、-炉 + k/q 2)
Пусть с С R такое, что
中=гm叫(S3)
тогда
^+x-2f(m,n)=0 и ,=二1+,广"吧1,
MbL
очевидно, выбираем положительный корень. Используя (8.2) и (8.3),
полунаем
k — 1 < х < к. Таким образом, к — наименьшее положительное
целое:
:,большее
или равное х. После того как мы нашли А:, величины тип
могут
бьггь легко вычислены
из (8.1):
п 二 /(m, n), m = к — ri + 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(0) , one(l), two(2), 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()
Verylong one(1), two(2);
Verylong f, m, n, result;
cout << "Введите целое число :";
cin » f ;
InverseMap(f ,m,n);
cout << " Соответствующее m есть " << m << endl;
cout << " и n есть ” << n « endl;
Реаульнан
'ведите целое число: 123456789012345
Соответствующее m есть 8553526
и п есть 7159959
'So'So
'So
'So
8.23 Гёделева нумерация
Мы можем работать с алфавитом, который содержит только единственную кву, например |. Слова, созданные из этого алфавита (за исключением нус^^' слова), ~ это I,К,HI и т. д. Эти слова тривиальным способом могут ид(чгг1 F。 цироваться с натуральными числами 0,1,2,.... Такая предельная стан,」 ' зация «материала» может быть использована в некоторых рассуждениях ” другой стороны, часто бывает удобно избавиться от излишнего разнообр^э ‘ алфавита, состоящего из нескольких элементов."'
Использование алфавита, состоящего из одного элемента^ не влечет за,儲 бой какого-либо существенного ограничения. Мы можем связать слова W алфавите из N элементов с натуральными числами G(W) таким образом,心。 каждое натуральное число будет связано, самое большее, с одним словом. Ана логичные аргументы относятся к словам в алфавите, состоящем из одного Э4е мепта. Такое представление G называется геделевой нумерацией [14] (так水е используется термин арифметизация), a G(W) — гёделев номер слова П\ связанный с представлением G. Ниже перечислены условия арифметизации W:
Если 忆力恥,то G^)力 G(%2)・
Существует алгоритм, который для любого данного слова W вычисляет соответствующее натуральное число G(W) за конечное число шагов.
Для любого натурального числа п за конечное число шагов можно определить, является ли п гёделевым номером какого-либо слова W в алфавите А.
Существует алгоритм, который по гёделеву номеру слова W над А строит это слово W (которое определено однозначно согласно п. 1) за конечное число шагов.
Вот пример гёделевой нумерации. Рассмотрим алфавит с буквами "、仇 с. Слова строятся конкатенацией любого конечного набора этих букв, т. е. размещением этих букв одной за одной в строку. Например, abcbba — слово. Abi можем перенумеровать слова следующим образом:
Данному слову х^Х2 - -. ^П1 где каждое Xi ~ а, b или с, мы присваиваем в качестве номера число
2do * 3d1 * • • • **\
где pi есть z-е простое число (и 2 есть 0-е простое число) и
1
1,
если
3
есть
出
& := <
2-
если
есть
仇
1
з,
если
37%
есть
с.
Пустому слову в качестве номера присваивается число 0.
Например, слово acbcc имеет номер 21 *33*52*73 = 463050, и abc имеет ном「? 21 * З2 * 53 = 2250. Число 7350 представляет aabb, так как 7350 — 21 +31 * 52 步 /Л
Чтобы показать, что эта нумерация удовлетворяет описанным выше кр"~ териям, мы используем основную теорему арифметики-.
^^ , натуральное число > 2 можно представить как произведение про- "紡 ел и это произведение, с точностью до порядка сомножителей,
можем перенумеровать любые типы объектов, а не только слова из
М1^ целом, критерии для полезной нумерации таковы:
6уКВ・ b '
Никакие два объекта не имеют одинаковый номер.
' любого объекта мы можем «эффективно» найти его номер.
Пття любого натурального числа мы можем «эффективно» определить,
является ли оно номером какого-либо объекта и, если это так, найти этот объект.
Ниже мы помещаем класс 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.hH
class Goedel
private:
// Поля данных
Verylong nvalue;
String wvalue;
Et is_G;
public:
// Конструкторы
Goedel();
Goedel(String);
Goedel(Verylong);
// Методы
Verylong number() const;
String word() const;
int is_goedel() const;
void rename(String);
"Void resize(Verylong);
Goedel::Goedel()
"nvaiue(Verylong("0")), wvalue(String("")), is_G(l) {}
Goedel::Goedel(String s)
:nvalue(Verylong('* 1")), wvalue (s) , is_G⑴
static Prime p(100);
Verylong temp, prim, zero("0"), one(”i"); p.run();
for (int i=0; i<s. lengthO ; i++)
if (s[i]>=,a, && s[i]<=,c,)
temp = one;
prim = Verylong(p(i));
for (char j = & ; j<=s [i] ; j++) temp *= prim; nvalue *= temp;
else
nvalue = zero;
break;
Goedel::Goedel(Verylong num) : nvalue(num), wvalue(String("1
static Prime p(100);
static Verylong zero("0") , one("l“);
Verylong prim;
int i=0, factor;
p.run();
while (num>one)
{
factor = 0; prim = Verylong(p(i));
while (num % prim == zero) { num /= prim; ++factor; } switch (factor)
wvalue=wvalue+String(na");break;
wvalue=wvalue+String("b*');break;
wvalue=wvalue+String("c");break;
default: is_G=0; return;
++i;
}
is_G = 1;
}}
}
}
1 п£г Goedel: : number () const { return nvalue; Ver#。В
5 Goedel::word() const
String
{(is_G) return wvalue;
Jeturn StringC");
}
jut Goedel::is_goedel() const { return is_G; }
void Goedel::rename(String s)
{ static Prime p(100);
static Verylong zero("0") , one("l");
Veryl°ng temp, prim;
p.run();
nvalue 二 one;
wvalue = s;
is_G = 1 ;
for (int i=0; i<s.length(); i++)
if (s[i]>=J aJ && s[i]<='c‘)
temp = one;
prim = Verylong(pd));
for (char j = ,aJ ; j<=s [i] ; j++) temp *= prim nvalue *= temp;
else
nvalue = zero;
break;
}
;oid Goedel::resize(Verylong num)
static Verylong zero(n0") , one("l");
assert(num >= zero);
if (num == one) { is_G = 0; return; }
static Prime p(100);
int i=0, factor;
Verylong prim;
p.runO ;
nvalue = num; wvalue = StringC"');
while (num>one)
{
factor = 0; prim = Verylong(p(i));
while (num % prim == zero) { num /= prim; ++factor; } switch (factor)
wvalue=wvalue+String("a");break;
wvalue=wvalue+String("b");break;
wvalue=wvalue+String("c");break;
default: is_G=0; return;
++i;
is_G = 1;
#endif
Далее рассмотрим простую программу, которая вычисляет соотв щие гёделевы номера слов acbc и aabb. Она также перечисляет все i номера, меньшие 1500:
// sgoedell.схх
#include <iostream.h>
#include "Goedel.h"
void main()
Goedel g(String(nacbcn));
cout << "Для слова '* << g.word();
cout << ” соответствующий гёделев номер есть
< g.number() << endl;
cout << endl;
g.rename("aabb");
cout << “ Для слова ” << g.word();
cout << " соответствующий гёделев номер есть
< g.number() << endl;
cout << endl;
g.rename("abcbc");
cout « cout « <<cout « cout « <<“ Для слова " << g.word();
cout « cout « <<
cout « cout « <<
"соответствующий гёделев номер есть g.number() << endl;
cOut « endl;cOut « endl;Qoedel h;
cOut « endl;
cOut « endl;
〃 Список всех гёделевым номеров, меньших 1500
for (int i二。;i《I500; i++)
h.resized);
if (h.is_goedel())
cout << i << “ => “ << “ гёделев номер для ”
< h.word()<< endl;
}
Результат
Для слова acbc соответствующий гёделев номер есть 463050
Для слова aabb соответствующий геделев номер есть 7350
0 :2 :4 :6 :8 :1218240 :2 :4 :6 :8 :121824гёделев номерДлягёделев номердля агёделев номердля bгёделев номердля аагёделев номердля сгёделев номерДлягёделев номердля агёделев номердля bгёделев номердля аагёделев номердля с30 : 36 : 54 : 60 : 72 : 90 : 108 120 150 180210 216 27030 : 36 : 54 : 60 : 72 : 90 : 108 120 150 180210 216 270гёделев номер для Ьа гёделев номер для abгёделевномерДлясагёделевномерДляааагёделевномерДляbbгёделевномерДляасгёделевномердляbaaгёделевномерДляcbгёделевномерДляabaГёделе!3 HOM6J)ДЛ5I beгёделев номер для Ьа гёделев номер для abгёделевномерДлясагёделевномерДляааагёделевномерДляbbгёделевномерДляасгёделевномердляbaaгёделевномерДляcbгёделевномерДляabaГёделе!3 HOM6J)ДЛ5I beгёделев гёделев гёделев гёделев гёделев гёделевгёделев гёделев гёделев гёделев гёделев гёделевномер номер номер номер номер номерномер номер номер номер номер номерДля ДляДля Для Для ДляДля ДляДля Для Для Длясаа aab bba аааа сс асасаа aab bba аааа сс асаДля слова abcbc соответствующий гёделев номер есть 14674275
0 :
2 :
4 :
6 :
8 :
12
18
24
0 :
2 :
4 :
6 :
8 :
12
18
24
гёделев номер
Для
гёделев номер
для а
гёделев номер
для b
гёделев номер
для аа
гёделев номер
для с
гёделев номер
Для
гёделев номер
для а
гёделев номер
для b
гёделев номер
для аа
гёделев номер
для с
30 : 36 : 54 : 60 : 72 : 90 : 108 120 150 180
210 216 270
30 : 36 : 54 : 60 : 72 : 90 : 108 120 150 180
210 216 270
гёделев номер для Ьа гёделев номер для ab
гёделев
номер
Для
са
гёделев
номер
Для
ааа
гёделев
номер
Для
bb
гёделев
номер
Для
ас
гёделев
номер
для
baa
гёделев
номер
Для
cb
гёделев
номер
Для
aba
Гёделе!
3 HOM6J
)ДЛ5
I be
гёделев номер для Ьа гёделев номер для ab
гёделев
номер
Для
са
гёделев
номер
Для
ааа
гёделев
номер
Для
bb
гёделев
номер
Для
ас
гёделев
номер
для
baa
гёделев
номер
Для
cb
гёделев
номер
Для
aba
Гёделе!
3 HOM6J
)ДЛ5
I be
гёделев гёделев гёделев гёделев гёделев гёделев
гёделев гёделев гёделев гёделев гёделев гёделев
номер номер номер номер номер номер
номер номер номер номер номер номер
Для Для
Для Для Для Для
Для Для
Для Для Для Для
саа aab bba аааа сс аса
саа aab bba аааа сс аса
300 =>гёделев номер для bab360 =>гёделев номер для cba420 =>гёделев номер для Ьааа450 =>гёделев номер для abb540 =>гёделев номер для Ьса600 =>гёделев номер для cab630 =>гёделев номер для abaa750 =>гёделев номер для аас840 =>гёделев номер для сааа900 =>гёделев номер для ЪЬЪ1050 =〉• гёделев номер для aaba1080 =〉• гёделев номер для сса1260 =〉• гёделев номер для bbaa1350 =〉» гёделев номер для acb1470 A> гёделев номер для aaab300 =>гёделев номер для bab360 =>гёделев номер для cba420 =>гёделев номер для Ьааа450 =>гёделев номер для abb540 =>гёделев номер для Ьса600 =>гёделев номер для cab630 =>гёделев номер для abaa750 =>гёделев номер для аас840 =>гёделев номер для сааа900 =>гёделев номер для ЪЬЪ1050 =〉• гёделев номер для aaba1080 =〉• гёделев номер для сса1260 =〉• гёделев номер для bbaa1350 =〉» гёделев номер для acb1470 A> гёделев номер для aaab
300 =>
гёделев номер для bab
360 =>
гёделев номер для cba
420 =>
гёделев номер для Ьааа
450 =>
гёделев номер для abb
540 =>
гёделев номер для Ьса
600 =>
гёделев номер для cab
630 =>
гёделев номер для abaa
750 =>
гёделев номер для аас
840 =>
гёделев номер для сааа
900 =>
гёделев номер для ЪЬЪ
1050 =〉
• гёделев номер для aaba
1080 =〉
• гёделев номер для сса
1260 =〉
• гёделев номер для bbaa
1350 =〉
» гёделев номер для acb
1470 A
> гёделев номер для aaab
300 =>
гёделев номер для bab
360 =>
гёделев номер для cba
420 =>
гёделев номер для Ьааа
450 =>
гёделев номер для abb
540 =>
гёделев номер для Ьса
600 =>
гёделев номер для cab
630 =>
гёделев номер для abaa
750 =>
гёделев номер для аас
840 =>
гёделев номер для сааа
900 =>
гёделев номер для ЪЬЪ
1050 =〉
• гёделев номер для aaba
1080 =〉
• гёделев номер для сса
1260 =〉
• гёделев номер для bbaa
1350 =〉
» гёделев номер для acb
1470 A
> гёделев номер для aaab
Теперь рассмотрим другой пример. Он вычисляет гёделев номер для строкам аа, ааа, аааа, .... Отметим, что некоторые гёделевы номера превышают границу для встроенного типа unsigned long.
// sgoedel2.схх
#include <iostream.h>
#include "MString.h'1
#include "Goedel.h"
void main()
String s;
Goedel g;
for (int i=l; i<=20; i++)
s = s + StringLa,);
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
caaaaaaa => 9699690
3 Maaaaaa => 223092870
? a aaaaaaaaa => 6469693230
]aaaaaaaa => 200560490130
'aaaaaaaaaa => 7420738134810
\aaaaaaaaaaa => 304250263527210
:aaaaaaaaaaaaa => 13082761331670030
I aaaaaaaaaaaaaaa => 614889782588491410
^ aaaaaaaaaaaaaaaa => 32589158477190044730
;了 aaaaaaaaaaaaaaaaa => 1922760350154212639070
8 aaaaaaaaaaaaaaaaaa => 117288381359406970983270
19 aaaaaaaaaaaaaaaaaaa => 7858321551080267055879090
20 aaaaaaaaaaaaaaaaaaaa => 557940830126698960967415390
8.3. Классы Verylong и Rational
8・3・L Логистическое отображение
Догистическое отображение / : [0,1] т [0,1] задается формулой
/(£)二 4а;(1 — х).
Оно может быть также записано в виде конечно-разностного уравнения 3十1=4勺(1一勺),力= 0,1,2,..., для ж£ e [0,1],
где xt W [0,1] для всех i € N U {0}. Пусть задано начальное значение х()— 1/3.
Тогда
83262727250432
= —, Хо = —,3 = , Хл =,....
9'816561'4304672Г
Это — так называемая хаотичная орбита [51]. Точное решение логистического отображения задается формулой
3 = Т - g cos(2£ arccos(l - 2з:0)).
В зависимости от типа £о мы можем получить приближенную или точную орбиту отображения. В случае приближенной орбиты мы используем для а;о встроенный тип double, тогда как для точной орбиты логистического отобра- 冰ення мы используем класс Rational, который построили в гл. 6.
Мн продемонстрируем особенности класса Rational, а заодно и его связи ° Классом Verylong. Мы сравним Rational<long> и Rational<Verylong> на Итерациях отображения.
Из полученных результатов мы видим, что длины числителя и зна- Менатела увеличиваются так быстро, что Rat ional < 1 ong> мог отображать результат только до четвертой итерации. Отсюда видно, что тип данных ational<Verylong> очень полезен для подобных вычислений.
Очень часто по большим числителю и знаменателю дроби трудно судить о ее Величине; в этом смысле представление чисел с плавающей запятой пред- Поч™тельнее. Оператор преобразования к типу double в классе обеспечивает Зкую возможность.
// logistic.схх
#include "Rational.h"
#include "Verylong.h"
void main()
{
Rationaleint> a(4,1), b(l,1), x0(l,3);//начальна величина х0=1/^ int i;
cout << "x[0]工"<< xO << '* или ” << double (x0) << endl ;
for(i=l; i<=4; i++) // нельзя использовать величину >4
{// из-за диапазона типа данных int
хО = а*х0*(Ь - хО);
cout << "х[" << i << "]=" << хО << ” или ” << double(хО) << endl,
cout << endl;
Rational<Verylong> cl("I","I"), c2("4",nl"),
y0("l”,"3“);// начальная величина y0 = 1/3
cout << '*y [0] = ” << yO << ” или ” << double (y0) << endl;
for (i=l; i<=6; i++) {
yO = c2*y0*(ci - yO);
cout << "y[" << i << "]=*' << yO << " или ** << double(yO) << endl;
Результат
x[0] = 1/3 или 0.333333
x[l] = 8/9 или 0.888889
x⑵=32/81 или 0.395062
x[3] = 6272/6561 или 0.955952
x[4] = 7250432/43046721 или 0.168432
у[0] = 1/3 или 0.333333
у[1] = 8/9 или 0.888889
у[2] = 32/81 или 0.395062
у[3] = 6272/6561 или 0.955952
у[4] = 7250432/43046721 или 0.168432
у[5] = 1038154236987392/1853020188851841 или 0.56025
у[6] = 3383826162019367796397224108032
/3433683820292512484657849089281 или 0.98548
I Теорема о сжимающем отображении
S —замкнутое множество в полном метрическом пространстве X. Сжи- пу^^ отображением называется отображение / : S т S, такое, что "°d(/(f) J(U)) S kd(x,y)^ <к <1, где d— расстояние в X.
О немО немговорятговорятТеорема (вио однуТеорема (вио однуоо、также так: «/ — липшицево порядка к < 1».сжимающем отображении. Сжимающее отображение / имеет、также так: «/ — липшицево порядка к < 1».сжимающем отображении. Сжимающее отображение / имеетнеподвижную точку, т. е. существует одна и только одна точка ②*,неподвижную точку, т. е. существует одна и только одна точка ②*,-!акая.-!акая.,что,что
О нем
О нем
говорят
говорят
Теорема (вио одну
Теорема (вио одну
о
о
、также так: «/ — липшицево порядка к < 1».
сжимающем отображении. Сжимающее отображение / имеет
、также так: «/ — липшицево порядка к < 1».
сжимающем отображении. Сжимающее отображение / имеет
неподвижную точку, т. е. существует одна и только одна точка ②*,
неподвижную точку, т. е. существует одна и только одна точка ②*,
-!акая.
-!акая.
,что
,что
В доказательстве используется последовательность итераций. Пусть х W S. Тогда
“Со) е S,... ,*)(% = 〃*т)(%) € 5
и
d(*)(C。)J(”T)(C。))W ^(*Т)(% ,*-2)(20)) W W 声-^(〃窃),%.
Так как А; < 1, последовательность /(п)(±о) является последовательностью Коти и поэтому, когда 几 стремится к бесконечности, сходится к пределу г* W S:
е* = lim /(n)(±o)= lim /(/(п7)(±о)) =/(◎*). п—юотгтоо
Единственность х* вытекает из определяющего свойства сжимающих отображений. Допустим, что есть другая точка у*, такая, что у* = /(у*), тогда
d(/(U*)JQ*))=d”*).
С другой стороны, d(/(y*),/(◎*)) < Ы(у*,х*), где k V L Следовательно, d(,衣*) = 0 и у* х*. □一
В следующей программе мы рассматриваем отображение / : (0,1] т (0,1]
5〃±) = -х(1 - х).
иидно, что отображение / имеет устойчивую неподвижную точку х* = 3/5. Мн применяем теорему о сжимающем отображении с начальным значением 3 = 9/10. Краткое описание программы дано ниже:
Из полученных результатов видно, что численная величина / сходится и это согласуется с нашим обсуждением. Тем не менее числители и знамен 敢 °Q рациональных величин (точные величины) на итерациях растут очень бЛ^^ч
// contract.схх
#include <iostream.h>
#include "Rational.h"
#include "Verylong.h"
class Map {
private:
Rational<Verylong> (*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)(Rational<Verylong>), 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 = ahs((value-temp)/temp); // относительная разность
cout << "Точка:м << value << " или ” << double(value) << endl; cout << endl;
} while (douhle(dist) > le-5);
}
// f(x) = 5/2 x(l-x)
Rational<Verylong> mapping(Rational<Verylong> x) {
return (Rational<Verylong>("5","2")*x*(Rational<Verylong>("1")^x));
“Id main。
(яаЧальная точка = 9/10
〃 g(mapping, Rational<Verylong>(,,9,* /^O'O);
费s-FP();
результат
те : 9/40 или 0.225
Tog: 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
Блуждающие решения
Рассмотрим обыкновенное дифференциальное уравнение
с начальным условием u(0) = и° > 0. Граничные точки задаются соотношениями
и* = 0, и* 二 1.
ГраничнаяГраничнаяточка и*точка и*Ренциального уравнения естьРенциального уравнения есть=1 асимптотически устойчива. Точное решение диффе-=1 асимптотически устойчива. Точное решение диффе-
Граничная
Граничная
точка и*
точка и*
Ренциального уравнения есть
Ренциального уравнения есть
=1 асимптотически устойчива. Точное решение диффе-
=1 асимптотически устойчива. Точное решение диффе-
и(土) = г для 0 < u0 < 1.
1 - Uo 4- woe£
Точное решение, начинающееся из начального значения и。= 99/100 = 0.99, Монотонно возрастает и сходится к 1 при t —> оо. Для £ = In 99 у 4.5951 мы ВЬ1нисляем
и«) = 0.9999.
Итак, и(土) уже достаточно близка к асимптотически устойчивой граничной Toq ке u* = 1. Для того чтобы интегрировать это уравнение конечно-разностнь ” методом, мы используем схему центральных разностей:
(8.4)(8.4)^д = Un(] — ^n)= 1? 2,3,...
(8.4)
(8.4)
с начальными условиями u° = 土。, u】=хо4-Лхо(1—x0). Это конечно-разностное уравнение второго порядка может быть переписано как система двух разност ных уравнений первого порядка:
un+l = Un,
Un+1 = ^n + 2/lUn(l — U^) ДЛЯ 72 = 1, 2, 3,...
c Ui = и。= ±o и ui = ±o + Лх0(1 ~ хо). Мы итерируем систему нелинейных дифференциальных уравнений, используя классы Rational и Verylong с начальным значением х0 = 99/100 и шагом сетки по времени h = 0.1. Эта система не сходится в граничной точке u* = 1, а демонстрирует колебательное поведение. Такой феномен имеет название блуждающее решение (также используется название ложное решение).
// ghost.срр
#include#include#include#include#include#include#include#include<fstream.h>
#include
#include
#include
#include
#include
#include
#include
#include
<math.h>
"Rational.h"
"Verylong.h"
// шаг сетки по времени // начальное значение// шаг сетки по времени // начальное значениеconst Rational<Verylong> h(”l","10");
// шаг сетки по времени // начальное значение
// шаг сетки по времени // начальное значение
const Rat i onal < Ver у 1 ong> х0(и99и ,и10011);
void main()
Rational<Verylong> u, v, ul, twoh, t,
zeroC'-O") , one(”L), two(n2") , fifty(“50");ofstream soutC'ghosts.dat"); // содержит рациональные величины// начальные значения
u = хО; 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;
Для 0 < t < 1.4 численное решение дает хорошую аппроксимацию точно- 「о решения. Решение и。монотонно увеличивается, достигая значения 1. После 力二 1.5 численное решение более не монотонно. При t = 3.0 величина и。впервые становится больше чем 1, и решение в силу этого начинает колебаться. Амплитуда колебаний становится все большей и большей. Она растет геометрически, н скорость прироста такова, что амплитуда умножается на е а 2.71828 за каж~ дую единицу приращения t. Когда t = 10, колебание теряет свою симметрию относительно u* = 1. Повторение таких циклов выглядит почти периодич- НЬ1М. Блуждающие решения появляются даже при совсем малых Л. Одна из причин этого феномена в том, что схема центральных разностей является разностной схемой второго порядка и что нестабильность наступает при = 1 и и* — 0. Глобальное поведение вычисленного решения очень чувствительно к начальным условиям и шагу сетки по времени. Чтобы показать, что поведение (&4) не обусловлено ограниченной точностью разрядной сетки компьютера, мы повторно решаем уравнения, используя точную арифметику (ghost.схх) и, чтобы избежать ошибок округления, применяем классы Rational и Verylong. Из полученного результата мы видим, что колебательное поведение имеет место даже для точных арифметик. Оно является унаследованной особенностью конечно-разностного уравнения.
Системы итерационных функций
Система гиперболических итерационных функций [5] состоит из полного метрического пространства (X, d) вместе с конечным набором сжимающих отображений wn : X —> X с коэффициентами сжатия sn для n = 1, 2,... , ^. Обозначим систему итерационных функций как {X : w^m = 1,2,... 7 N} и введем коэффициент сжатия
s = max{sn : n = 1,2,N}.
Пусть (X, d) — полное метрическое пространство и (Н(Х),九(d)) обозначает соответствующее пространство непустых компактных подмножеств с метрикой Хаусдорфа h(d). Следующая теорема суммирует факты о системе гиперболических итерационных функций:
Теорема. Пусть
{X : wn?n = 1, 2,... , N}
есть система гиперболических итерационных функций с коэффициентом с^сатия s, тогда преобразование W : Н(Х) —> Н(Х); определенное как
N
W(B) := U 5(B)
°"兄 всех В W 咒(X), есть сжимающее отображение полного метрического пРостранства (Н(Х), h(d)) с коэффициентом сжатия s, m. е.
h(W(B),W(C)) Ws・/i(B,C)
для всех В\С G 丸(X). Его единственная неподвижная точка, Л с 〃(* удовлетворяет соотношению丿,
N
А :=勿⑷=(J g⑷
и является пределом
4:=做川n)⑻
для любого В W Н(Х).
Неподвижная точка А £ Н(Х) называется аттрактором системы итера ционных функций. За доказательством мы отсылаем к отличной книге Барнсли [5].
Далее мы представляем интересный пример из фракталов ——стандартное множество Кантора.
Стандартное множество Кантора описывается следующей системой итерационных функций:
112
{R : wi,w2}, где wi(x) := -х и w2(x) := -х + -.
Это — система итерационных функций с коэффициентом сжатия s = 1/3. Положим
Во = [0,1] и Bn = W^n\B0).
Тогда В = Ншп^-оо Вп есть стандартное множество Кантора. Оно также называется множеством Кантора средней трети или троичным множеством Кантора. Множества Во, 6】,82, • ■ • задаются следующим образом:
瓦=[0,1],
当=同唱1
82 —82 —°4°4ииии8 ; 9}18 ; 9}1
82 —
82 —
°4
°4
и
и
и
и
8 ; 9}1
8 ; 9}1
Это означает, что мы удаляем открытую среднюю треть, т. е. интервал (|; |) 4a первом шаге, и удаляем пару интервалов (|, |) и (j, |) на втором шаге.
О
11 211 219 2025 2619 2025 26
1
1 2
1
1 2
19 2025 26
19 2025 26
»ис・ 8.1. Построение множества Кантора.
должая таким образом удалять среднюю треть, мы приходим в пределе к 吗水еству Кантора при и т сю (рис. 8.1).
МН%!»ндартное множество Кантора имеет мощность с, совершенно, нигде не 01rHо и имеет нулевую меру Лебега. Каждое х G В может быть записано в
а: — di3~1 + a??-? + &зЗ~з 4-... , где 出 € {0, 2}.
Соответствующая функция Кантора называется лестницей дьявола.
В следующей программе мы генерируемем множества Кантора Вп для п — ]2 3,4,5,6 с классами Verylong и Rational:
〃 Cantor.схх
include <iostream.h>
#iiiclude "Rational .h"
#include "Verylong.h"
#include "Vector.h"
const Rational<Verylong>a=Rational<Verylong>(0) ; // нижняя граница const Rational<Verylong>h=Rational<Verylong>(l) ; // верхняя граница
class Cantor
private:
Vector<Rat ional<Verylong> > CS;
int currentSize;
public:
Cantor(int);
Cantor(const Cantor &);
int step();
void run();
friend ostream & operator « (ostream &, const Cantor &);
};
Cantor::Cantor(int numStep)
:CS(pow(2,numStep+l)), currentSize(2)
{ CS[O] = a; CS[1] = b; }
CantorCantor(const Cantor & s)
:CS(s.CS), currentsize(s,currentSize) {}
;nt Cantor::step()
int i, newSize;
static Rational<Verylong> three(3), tt(2,3);
static int maxSize = CS.length();
if(currentsize < maxSize)
for (i=0; i<currentSize; 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 (step()) ; }
ostream & operator << (ostream & s, const Cantor &
for (int i=0; i<c.currentSize; i+=2)
s « ”[“ « c.CS[i] « ” ";
s « c.CS[i+l] « "]";
return s;
void main()
const int N = 6;
Cantor C(N);
cout << C << endl;
for(int i=0; i<N; i++)
C.stepO ;
cout << C << 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
8 4 ] Логистическое отображение и показатель Ляпунова
9ToM разделе мы вычисляем показатель Ляпунова логистического отобра- 'ей0Я [51], которое задается соотношениями
д+1 = 4д(1 - д),
力二 0,12 … н £о W [0,1]. Вариационное уравнение логистического отобра- ^ЙЯ (также называемое линеаризованным уравнением) определяется соот-
чтениями
亚+1 =方"块'
£ dx£ dx= 4-8%= 4-8%
£ dx
£ dx
= 4-8%
= 4-8%
где
Так как
из него следует, что
/(х) = 4x(1 —吟
Ш+i = (4 - 8д)块
с Уо 力 0・ Показатель Ляпунова А определяется как
》(①o,Uo)=」im ~ In — , уо В 0.
Ттоо Т I у0 I
Мы находам, что для почти всех начальных значений показатель Ляпунова задается формулой
А = In 2.
Мы итерируем логистическое отображение и вариационное уравнение, чтобы найти показатель Ляпунова. Следующая программа использует классы Verylong$, \verbRational— и Derive, чтобы аппроксимировать показатель Ляпунова, Класс Derive используется также и для дифференцирования. Та- ким образом, вариационное уравнение получается путем точного дифференцирования.
〃 Ljapunov.срр ^include <iostream.h> ^include <math.. h> #include "Verylong.h" "include "Rational.h" "include "Derive.h"
void main()
int N = 100;
double xl, х = 1.0/3.0; double у = 1,0;
Derive<douhle> Cl(1.0);// константа 1.0
Derive<double> С4(4.0);// константа 4.0
Derive<douhle> X;
cout << "i = 0 х = " << х << " " << "у = " << у << endl; for (int i=l; i<=N; i++)
xl = X;
x = 4.0*xl*(1.0 - xl);
X.set(xl);
Derive<douhle> Y = C4*X*(Cl - X);
у = df(Y)*y; cout « "i = " « i « ” ”
<< "x = " << x « " " << "y = " << у << endl;
double lam = log(fabs(y))/N;
cout << "Аппроксимирующая величина для лямбды = ” << lam << cout << endl;
int M = 9;
Rational<Verylong> ul;
Rational<Verylong> u("ln,"3"), v("l“);
Rational<Verylong> Kl(“l");
Rational<Verylong> K2(”4");
Derive<Rational<Verylong> > DI(Ki); // константа 1
Derive<Rational<Verylong> > D4(K2); // константа 4 Derive<Rational<Verylong> > U;
cout << nj = 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<Rational<Verylong> > V = D4*U*(D1 - U);
v = df(V)*v;
cout << "j = " << j << ” ”
<< "u = " << u << " " << "v = " << v « endl;
}
la汐匸 1og(tabs(double(v)))/M;
cout у "Аппроксимирующая величина для лямбды = " « lam «
产 уВ::
/ 0
X
=
0.333333
У =
1
X
二
0.888889
У =
1.33333
,2
X
=
0.395062
У =
-4.14815
.=3
X
=
0.955952
У =
-3.4824
i = 4
X
二
0.168432
У =
12.7024
л = 96
X
=
0.298676
У =
-7.69211е+28
2 = 97
X
=
0.837875
У =
-1.23888е+29
i 二 98
X
=
0.543363
У =
3.3487е+29
i 二 99
X
=
0.992479
У =
-1.16167е+29
;=100
X
=
0.0298588
У =
4.57677е+29
длпроксимирующая величина
для лямбды = 0.68296
0
и 二
1/3
v = 1
1
и =
8/9
v = 4/3
2
u =
32/81
v = -112/27
3
u =
6272/6561
v = -7616/2187
4
u =
7250432/43046721
v = 182266112/14348907
Аппроксимирующая величина для лямбды = 0.69225
Классы Verylong, Rational и Complex
Множество Мандельброта
Пусть С — комплексная плоскость, тогда множество Мандельброта М определяется как
А/ := {с W С : с, с2 + с, (с2 + с)? + с, ... /> оо}.
Утобы найти множество Мандельброта, мы изучим рекурсивное соотношение
z±+i =靖 + е, ^ — 0,15 2 ...,
сначальным значением го = 0. Так как z := n + W и с := q +£с2 с n,у, q, С2 € R, ^1 Можем переписать соотношение рекурсии как
g+1 = £;一疗 + 5,
亚+i = 2夠亚+C2,
Началы1ым значением (хо,^о) = (0,0). Рассмотрим теперь некоторые точки 曹 Комплексной плоскости С и определим, принадлежат ли они множеству Мандельброта, М:
(ci,c2) = (0,0) принадлежит М,
(口,。2)二(1/4,1/4) принадлежит М,
(сь С2)= (1 /2, 0) не принадлежит М, поскольку
八1317
2о = °, Z1 =不 Z2 =彳,Z3 = ^,…Т ОО・
2410
11 1548
с 马(н) = 1 и Pi (х) — х. Производящая функция для многочленов Ле;^ задается соотношением"°口;
1,1 二^N + 力 21,1 二^N + 力 271=071=0
1
,1 二^N + 力 2
1
,1 二^N + 力 2
71=0
71=0
В следующей программе мы используем рекуррентное соотношение, чтобы、 нерировать несколько первых многочленов Лежандра‘
Р^)二 1,尸 1(N)= N, B(N)= I(За;2 - 1),
Рз(с) = gW — Зн), 马(n)二 ^(35х4 — ЗОх2 + 3).
Мы также показываем справедливость дифференциального уравнения Лежан дра для случая п = 4.
// Legendre.h
#include#include#include#include#include#include#include#include<iostream.h>
#include
#include
#include
#include
#include
#include
#include
#include
<assert.h> "MSymbol.h" ''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> > & kernaD
:mcixTerm(n), currentstep(0) , P(l), x(kernal) {}
int Legendre::step()
int prev = currentstep;
Sum<Rational<iiit> > R,one(l);
++currentStep;
好(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 (step()) ; }
void Legendre::reset ()
currentstep = 0;
P = Rational<int>(1);
}
Sum<Rational<int> > Legendre::current() const
{ return P; }
Sum<Rational<int> > Legendre::operator () (int m) {
assert(m <= maxTerm);
reset();
for (int i=0; i<m; i++) stepO;
return P:
°stream & operator << (ostream & s, const Legendre & L)
{ return s « L.P; }
〃 Legendre.cxx
:include <iostream.h>
:include "MSymbol.h" include "Rational.h" include "Legendre,h"
void main() {
int n=4;
Sum<Rational<int> > x("x",0), one(l);
Legendre P(n,x);
// Вычисляем несколько первых многочленов Лежандра cout << "Р(0) = 11 << Р << endl;
for (int i=l; i<=n; i++) {
P.stepO ;
cout << "P("<< i << ") = " << P << endl;
cout << endl;
// Другой способ получения многочленов Лежандра
cout <<"P(l)="<<Р(1)<<endl;
cout <<"Р(2)="<<Р(2)<<endl;
cout <<”Р(3)="<<Р(3)<<endl;
cout <<"Р(4)="<<Р(4)<<endl; cout << endl;
// Покажем, что для п=4,
// Дифференциальное уравнение Лежандра выполняется
Sum<Rational<int> > result;
result = df((one-x*x)*df(P.current(),x),x)
+ Sum<Rational<int> >(n*(n+1))*P.current();
cout << result << endl; // ==> 0 }
Ассоциированные функции Лежандра
Имея решение дифференциального уравнения Лежандра, мы теперь сможем получить решение уравнения
//2\
姦 - 石 ++ 1) -⑵=。,
где т.п — константы и m не обязательно равно нулю. Решения этого уравнения Pi^(x) называются ассоциированными функциями Лежандра степени п (??二 0,1,2,...) и порядка \т\ < п. Они определяются соотношениями
用叫2) = (1 一川产/2备Р.⑺,侧=0,1,2,…,二九 (8:))
Отметим, что для m = 0 мы имеем 琛(n) = %(化),которые являются многон」"” нами Лежандра. Функции Р尸 удовлетворяют рекуррентному соотношснн*'
(2п + 1)хР^(х) = (п - |m| + l)pj"(c) + (п + 侧)耳吧⑵
ИаКонеП-показать.股кан; 11KlИаКонеП-показать.股кан; 11Kl
ИаКонеП-показать.
股кан; 11Kl
ИаКонеП-показать.
股кан; 11Kl
2 5+卜"},
2储+ 1 (г —ь川)! 1
что /,|"“ образуют „о.'нкх1 множество в гильГх'р- 1). Несколько пгрвзх ассоциированных (|)yiii<niiii
F;“)="(1 - х-у/~. 比⑺=3(1 - .г2)
尸:(工)二*1,m)"‘(5,戸 一 D,八:(7)= 13/(1 一 产)、尸::(」')=15(1 一 戸)3/2.
В следуюык'иВ следуюык'ипрограмме мы ис1к).т1ьзу('Мпрограмме мы ис1к).т1ьзу('Ми mudioephw Лежандра, реа/пь
В следуюык'и
В следуюык'и
программе мы ис1к).т1ьзу('М
программе мы ис1к).т1ьзу('М
зованньк1 в иредпк'ств'г'ющсм раз/^е/к1 ,^;гя конструирования ассоциированных многочленов . И'жан.чра.
// AssLegendre,h #include <iostream.h> #include "MSymbol.h" #include "Rational.h" #include "Legendre.h"
class AssLegendre
private:
Sum<Rational<int> > P;
const Sum<Rat ional<int> > &x;
public:
AssLegendre(const Sum<Rational<int> > &);
AssLegendre(int, int, const Sum<Rational<int> > &);
void redefine(int, int);
Sum<Rat ionaKint > > 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(1);
Legendre L(1,x);
P 匚 L(l);
for (i=0 ; Kabsm; i++) P = df (P , x);
P power (one-x*x , Rat ionaKint> (absm, 2));
void AssLegendre::redefine(int 1, int m) {
int i, absm = abs(m);
Rational<int> one(1);
Legendre L(1,x);
P 二 L(l);
for (i=0; Kabsm; 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.h1*
#include 11 AssLegendre. h"
void main()
{
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,0) = 1
P(l,0)工 x
p(l,l) = (i-xF2))Fi/2)
P(2,0) = 3/2*x"(2)-l/2
P(2,l) = 3*х*(1-х"(2))Л(1/2)
P(2,2) = 3-3*хЛ(2)
0) = 5/2*х"(3)-3/2*х
Р;'1) = 15/2*хЛ(2)*(1-хЛ(2))"(1/2)-3/2*(1-х"(2))Л(1/2)
%‘2)二 15*х-15*х"(3)
Р 3 3) = 15*(1-х-(2))"(3/2)
Р:4‘0) = 35/8*хЛ(4)-15/4*х"(2)+3/8
Р:4‘1) = 35/2*хЛ(3)*(1-хЛ(2))Л(1/2)-15/2*х*(1-х"(2))"(1/2)
Р:4‘2)二 60*хЛ(2)-105/2*х"(4)-15/2
;4‘3)二 105*х*(1-х"(2))"(3/2)
;;4;4) = 105-210*х"(2)+105*х"(4)
Многочлены Лагерра
Многочлены Лагерра могут быть определены по формуле Родриго:
ьп(х) :=П = и J …
где Lo(x) — 1. Используя правило Лейбница^ получаем
L£h)=加
Многочлены Лагерра Ln — это решения линейного дифференциального уравнения второго порядка
(8.6)
Они удовлетворяют рекуррентному соотношению
Ln+i(x) - (2冗 + 1 - £)Ln(H)- n2Ln-i(x),
где Lo(^) = 1 и Li(n) — 1 — x. Многочлены Лагерра также могут быть определены своей производящей функцией
Соотношение ортогональности для многочленов Лагерра имеет вид
e~xLm(x)Ln(x)dx 二(n!)2Jmn.
того, мы имеем
° следующей программе мы используем рекуррентное соотношение, чтобы сге-
Н'Рировать несколько первых многочленов Лагерра. Мы также демонстриру- что (8.6) выполняется для л = 4. Несколько первых многочленов Лагерра суть
Lo(n) = 1, Li(n) = 1 — 亠 厶2(化)=2 — 4n + :工 2, 厶 a(N)= 6 — 18H + 9/ — /3, L4(x) = 24 — 96f + 72产—16x3 +
// Laguerre.схх
#include <iostream.h>
#include "MSymbol.h"
class Laguerre
{
private:
int maxTerm, currentstep;
Sum<int> P, Q;
const Sum<int> &x;
public:
Laguerre(int, const Sum<int> &);
int step();
void run();
Sum<int> current() const;
friend ostream & operator << (ostream &, const Laguerre };
Laguerre::Laguerre(int n, const Sum<int> & kernal) :maxTerm(n), currentstep(0), P(l), x(kernal) {}
int Laguerre::step() {
int prev = currentstep;
Sum<int> R;
++currentStep;
if (currentStep==l) { Q = i; P = 1-x; return 1; }
if (currentstep <= maxTerm) {
R = (2*prev+l-x)*P-prev*prev*Q;
Q = P; P = R;
return 1;
}
return 0;
}
void Laguerre::run() { while (step()) ; }
Sum<int> Laguerre::current() const { return P; }
ostream & operator << (ostream & s, const Laguerre & L)
{ return s << L.P; }
void main() {
sum<int> x("x",O);
^aguerre L(n,x);
,/ рычислим несколько первых многочленов Лагерра
cout << "L (0) = " << L << endl;
for (int i=l; i<=n; i++)
{
L.stepO ;
cout << "L("<< i << ") = " << L << endl;
cout << endl;
// Покажем, что для n=4,
// дифференциальное уравнения Лагерра выполняется Sum<int> result;
result 二 x*df(L.current(),x,2) + (l~x)*df(L.current(),x) +n*L.current();
cout << result << endl;
}
Результаты
L(O)二 1
L(l)二 1-x
L(2) = 2-4*x+『(2)
L(3)二 6-18*x+9*x^(2)-x"(3)
L(4)二 24-96*x+72*xY2)-16*xX3)+xC(4)
0
Многочлены Эрмита
^огочле-ны Эрмита опредс'.ляются как
…=tУ"言,
「Ae 九 二:().1.2Можно доказать, что они упонпгтнорзнзт лшк'йпому дпф- Ференциалык^му г-равненшо
——2 ж—— + 2 nH п — 0.Пспользуя ;iH(|)(|)epenu,iR^bH(X' уравнение, мы можем доюкзать, что многочлены ^Рмита()1)'г()г()п;1льны пл ( — ос,оо) с в('совс)н функцией 公一‘二 Мы имсч'М——2 ж—— + 2 nH п — 0.Пспользуя ;iH(|)(|)epenu,iR^bH(X' уравнение, мы можем доюкзать, что многочлены ^Рмита()1)'г()г()п;1льны пл ( — ос,оо) с в('совс)н функцией 公一‘二 Мы имсч'М(8-7)(8-7)OpTorolUUIblibl наOpTorolUUIblibl на
——2 ж—— + 2 nH п — 0.
Пспользуя ;iH(|)(|)epenu,iR^bH(X' уравнение, мы можем доюкзать, что многочлены ^Рмита()1)'г()г()п;1льны пл ( — ос,оо) с в('совс)н функцией 公一‘二 Мы имсч'М
——2 ж—— + 2 nH п — 0.
Пспользуя ;iH(|)(|)epenu,iR^bH(X' уравнение, мы можем доюкзать, что многочлены ^Рмита()1)'г()г()п;1льны пл ( — ос,оо) с в('совс)н функцией 公一‘二 Мы имсч'М
(8-7)
(8-7)
OpTorolUUIblibl на
OpTorolUUIblibl на
£нП⑺нm⑺аe = \/^2"『山5,门",
°"' ^, in =2 О. 1.2,. .. и 8〃7n есть дельта Кронекера. Рекуррентное соотноык'лие Мн()1'{)”」("н)в Эрмита приобретает вид
〃п+1(/) = 2//九(:г) 一 2"H『i(『):
где Но(н) = 1 и [(£)= 2H. Многочлены Эрмита могут быть также опред^ производящей функцией:"
£2£上一£2 = e71=0£2£上一£2 = e71=0斗⑺严斗⑺严n!n!
£2£上一£2 = e
71=0
£2£上一£2 = e
71=0
斗⑺严
斗⑺严
n!
n!
Кроме того, мы имеем
Г я (力山=0吐3 - н?+М°)厶-25+1)2(n+l),
В следующей программе мы используем рекуррентное соотношение, чтобы нерировать первые несколько многочленов Эрмита, и показываем справе вость (8.7) для п = 4:
Ho(N)二 1, H1(N)= 2n, 办⑺二 4* 一 2, Нз(с) = 8* - 12х, 儿⑺二 16d - 48* + 12, H5(x) = 32步 一 16" + i2(k.
// 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(0), P(l), x(kernal) {}
int Hermite::step() {
int prev = currentstep;
Sum<int> R;
++currentStep;
if(currentStep==l) {
Q = 1;
р 二 2*х; return 1;
if(currentstep <= maxTerm)
{ r 二 2*x*P-2*prev*Q;
Q = P; P = R; return 1;
return 0;
}
void Hermite::run()
{ while (stepO) ; }
Sum<int> Hermite::current() const
{ return P; }
ostream & operator << (ostream & s, const Hermite & H { return s << H.P; }
void main()
int n=4;
Sum<int> x("x",0);
Hermite H(n,x);
// Вычисляем несколько первых многочленов Эрмита cout « "Н(0) = " « Н « endl;
for (int i=l; i<=n; i++)
H. step();
cout << "H("<< i << ") = " « H « endl;
cout << endl;
// Показываем, что для n=4
// дифференциальное уравнение Эрмита вьшолняется. Sum<int> result;
result 二 df(H.current(),х,2) - 2*x*df(Н.current(),х + 2*n*H.current();
cout << result << endl;
Результат
Н(0)二 1
Н(1) = 2*х
Н(2) = 4*х"(2)-2
Н(3) = 8*х"(3)-12*х
Н(4)二 16*х^(4)-48*хЛ(2)+12
О
Многочлены Чебьппева
Многочлены Чебышева определяются соотношением
7\(е) := cos(Tiarccoso:), п = 0,1,2,....
Отметим, что Т_п(х) — 71n.(£)и из тригонометрических формул мы получаем
Tn+m(x) + Tn-m(x) - 2Тп(х)Тт(х).
Для m — 1 мы имеем рекуррентное соотношение
Тп+13) = 2%以(£)一 Tn_i(N),
где 7o(z) = 1 и Ti(£)= £• Таким образом можно последовательно вычислить все Тп(х). Пусть ж := cos(^), тогда
g 二容⑺=cos(nJ),
dy dx d2 y dx2dy dx d2 y dx2n sin(n^) sin 0 '~n2 cos(?i^) + n sin(n^) cot 8n sin(n^) sin 0 '~n2 cos(?i^) + n sin(n^) cot 8R2y , 1 -£2R2y , 1 -£2sin2 ex dysin2 ex dy1 — х2 dx'1 — х2 dx'
dy dx d2 y dx2
dy dx d2 y dx2
n sin(n^) sin 0 '
~n2 cos(?i^) + n sin(n^) cot 8
n sin(n^) sin 0 '
~n2 cos(?i^) + n sin(n^) cot 8
R2y , 1 -£2
R2y , 1 -£2
sin2 e
x dy
sin2 e
x dy
1 — х2 dx'
1 — х2 dx'
Видно, что многочлены 丁冗⑺ удовлетворяют обыкновенному линейному дн@- ференциальному уравнению второго порядка
(1 - *)需-“界 + ^у = О-
Первые несколько многочленов таковы:
丁0(£)= 1,Т](£)二 £,厶⑺=2Х2 - 1,
Т3(纪)=4£ - 3X, T4(e)=8d - 8х2 + 1.
// Chebyshev.схх
#include <iostream.h>
#include "MSymbol.h"
class Chebyshev
rivate:
iirt maxTerm, currentstep;
Sum<in‘> P, Q;
const Sum<int> &x;
)ublic:
Chebyshev(int, const Sum<int> &);
int stepO ;
void run();
Sum<int> current () const;
friend ostream & operator << (ostream &, const Chebyshev
触byshev::Chebyshev(int n, const Sum<int> & kernel) :maxTerm(n), currentStep(O) , P(l), x(kernel) {}
int Chebyshev: : stepO
{
Smn<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;
void Chebyshev::run()
{ While (step()); }
Sg^int〉Chebyshev: : current () const
{ return P; }
°stream & operator << (ostream & s, const Chebyshev & T)
{ return s << T.P; }
int n=4;
Sum<int> x(nx",0);
Chebyshev T(n,x);
// Вычисляем несколько первых многочленов Чебышева cout << ”Т(0) = " << Т << endl;
for (int i=l; i<=n; i++)
{
T.stepO ;
cout « nT(n« i « ") = " « T « endl;
}
cout << endl;
// Показываем, что для n=4
// дифференциальное уравнение Чебышева выполняется. Sum<int> result;
result = (1-х*х)*df(Т.current(),х,2) - x*df(Т.current(),j + n*n*T.current();
cout << result << endl;
Результат
T(0) = 1
T⑴=x
T(2) = 2*x"(2)-l
T(3) = 4*x"(3)-3*x
T(4) = 8*x"(4)-8*x"(2)+l
0
8.62 Вычисление кумулянтов
Пусть £, a7s 味 e R н
E泣=0E泣=0n\n\
E
泣=0
E
泣=0
n\
n\
c ao = 1- Мы найдем соотношения между коэффициентами an и 5П экспоненты с левой стороны равенства задается формулойc ao = 1- Мы найдем соотношения между коэффициентами an и 5П экспоненты с левой стороны равенства задается формулой
c ao = 1- Мы найдем соотношения между коэффициентами an и 5П экспоненты с левой стороны равенства задается формулой
c ao = 1- Мы найдем соотношения между коэффициентами an и 5П экспоненты с левой стороны равенства задается формулой
R!(乙:加丿 k! IR!(乙:加丿 k! IEE
R!(乙:加丿 k! I
R!(乙:加丿 k! I
E
E
1 oo oooo=总£ £…£711 =1 722 = 171& = 11 oo oooo=总£ £…£711 =1 722 = 171& = 1%也2 ...6“b+"2+ …+%也2 ...6“b+"2+ …+
1 oo oooo
=总£ £…£
711 =1 722 = 171& = 1
1 oo oooo
=总£ £…£
711 =1 722 = 171& = 1
%也2 ...6“b+"2+ …+
%也2 ...6“b+"2+ …+
)10Рателы1().Г ОС' IеХР 2^ ‘"!)10Рателы1().Г ОС' IеХР 2^ ‘"!1+玄纬+J^ п-1+玄纬+J^ п-П] =1 П2 = 1П] =1 П2 = 1
)10Рателы1().
Г ОС' IеХР 2^ ‘"!
)10Рателы1().
Г ОС' IеХР 2^ ‘"!
1+玄纬+J^ п-
1+玄纬+J^ п-
П] =1 П2 = 1
П] =1 П2 = 1
丄手手…手匕也2…%廿宀‘"宀“
+ k!乙厶 厶几1仿2!.…7心!
711 = 1 712=1九卜=1
Приравнивая члены одинакового порядка по £, мы можем получить соотноше- ^е между л я всех положительных целых п. Первые три коэффици- еята при степенях / eci ь
х1 : “Т =倂,/ : &2 = М + 片,£? : &3 二 Ьз + 36-2^1 + b;・
Отсюда следует, что
仇="т; б?=例—&;, Ьз = &з — За2al + 2а;.
В программе мы воспроизводим этот процесс получения коэффициентов. Он показывает, что система компьютерной алгебры в состоянии эффективно получать коэффици(нггы.
// сшпи. срр
<iostream.h> "MSymbol.h" "Vector.h" "Rational.h"<iostream.h> "MSymbol.h" "Vector.h" "Rational.h"#include #include #include #include
<iostream.h> "MSymbol.h" "Vector.h" "Rational.h"
<iostream.h> "MSymbol.h" "Vector.h" "Rational.h"
Sum<Rational<int> >
Taylor(Sum<Rational<int> > u, Sum<Rational<int> > & x, int n)
Rational<int> zero(0);
X.set(zero);
Sum<Rational<in't> > series (u. value ());
int j , f ac = 1;
for(j=l; j<=n; j++)
{
и = df (u, x) ; f ac = f ac * j ;
series += Rational<int>(lac) * u.value() * power(x,j);
return series;
Void main()
int i, fас, n=5;
Vector<Sum<Rational<int> > > a(n), b(n);
Sum<Rational<int> > x("x",0), у, P, Q,
aO(”aO”,O), al(”al\0), a2(”a2\0), a3("a3",0), a4("a4",0),
Ь0("Ь0”,0), bl("bl",0) , b2("b2",0), ЬЗ("ЬЗ",0) , Ь4("Ь4|,,0);
a[0] = aO; a[l] = al; a[2] = a2; a[3] = a3; a[4] = a4; b [0] = bO; b [1] = 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 << "Коэффициент при x :"<< endl;
cout << "exp(Q) => " << у.coeff(x,1) << endl;
cout << " P => " << P.coeff(x,1) << endl;
cout << endl;
cout << ” Коэффициент при x'2 :"<< endl;
cout << "exp(Q) => " << у.coeff(x,2) << endl;
cout << " P => 11 << P. coef f (x,2) << endl;
cout << endl;
cout << " Коэффициент при x*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) => " << у.coeff(x,4) << endl;
cout << " P => " « P.coeff(x,4) << endl;
Результат
P = аО+а1*х+1/2*а2*хЛ(2)+1/6*аЗ*хЛ(3)+1/24*а4*хЛ(4)
Q = Ь1*х+1/2*Ь2*хЛ(2)+1/6*ЬЗ*х"(3)+1/24*Ь4*хЛ(4)
Бейлора расширения exp(Q)=
°+ь1:кх+1/2*Ь1''(2)*х"(2)+1/2*Ь2*х"(2)+1/6*Ь1"(3)*х"(3) ^/2*Ь1*Ъ2*х"(3) + 1/6*ЬЗ*хл(3) + 1/24*Ь1"(4)*х"(4) )4*Ь「(2) *Ь2*х" (4) + 1/6*Ь1*ЬЗ*хЛ (4) + 1/8*Ь2Л (2) *хЛ(4) j24*b4*xF4)+l/120*b广(5)*xF5)+l/12*b「(3)*b2*xC(5) 1/12*Ы" (2) *ЬЗ*х" (5)+1/8*Ь1*Ь2Л (2) *х* (5) 1/24*Ъ1*Ъ4*х,' (5) + 1/12*Ь2*ЬЗ*х"(5)
аффшхиент при х :
,xp(Q)=> bl
р => а!
коэффициент при х"2 :
'xp(Q) => 1/2*Ъ1"(2) + 1/2*Ь2
р => 1/2*а2
;оэффициент при х"3 :
(xp(Q) => l/6*bl"(3)+l/2*bl*b2+l/6*b3
Р => 1/6*аЗ
коэффициент при хЛ4 :
$xp(Q) => 1/24*Ь1"(4)+1/4*ЫЛ(2)*Ь2+1/6*Ы*ЬЗ+1/8*Ь2Л(2)+1/24*Ь4
Р => 1/24*а4
1-6.3. Внешнее произведение
3 этом разделе мы дадим реализацию внешнего произведения (используется ^кже название «произведение Грассмана»), описанного в разд. 2.13. В про- рамме внешнее произведение обозначается значком *. Мы определяем детерминант 4x4 матрицы:
/ 1 2 5 2 \
4 —0 12 3
1010'
\ 0 3 0 7 /
Счисляя мнршнрс произведение
2、
3
о •
7丿
ei> ^2, e3je4 — стандартный = 24.
/ 1、о
1
\ч
А
/ 2
1
0
\ 3
Л
/ 5
2
1
\ о
\
Л
Внешнее произведение дает 24ei Л в2 Л е3 Л 64, где运зис в R1. Таким образом, мы находим, что det А
// Grass.срр
#include
#include
#include
#include
<iostream.h>
"Vector.h"
Matrix.h"
MSymbol .h*'
void main()
int i,j,n=4;
Sum<int> eOLeO'O), el("el",0),巳2('七2",0), e3("e3n,0 у,result;
Matrix<Sum<int> > A(n,n);
Vector<Sum<int> > e(n);
Commutative(int(),0);
AEO] [3] = 2;A[l] [3] = 3;A[2] [3] = 0;A[3] [3] = 7;AEO] [3] = 2;A[l] [3] = 3;A[2] [3] = 0;A[3] [3] = 7;A[0] [0]=1;A[0][1]=2;A[0] [2]=5;
AEO] [3] = 2;
A[l] [3] = 3;
A[2] [3] = 0;
A[3] [3] = 7;
AEO] [3] = 2;
A[l] [3] = 3;
A[2] [3] = 0;
A[3] [3] = 7;
A[l] [0]=0;A[l][1]二1;A[l] [2]=2;
A[2] [0]=1;A[2][1]=0;A[2] [2]二1;
A[3] [0]=0;A[3][1]二3;A[3] [2]=0;
e[0] = eO; e[1] = 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;
产y"吧
1*е0*е1+7*е0*е1*е0*еЗ+3*е0*е1*е2*е1+7*е0*е1*е2*еЗ 3*e^**e2*e0*el+14*e0*e2*e0*e3+6*e0*e2"(2)*el+14*e0*e2"(2)*еЗ +^*e^#e3*e0*el+21*e0*e3*e0*e3+9*e0*e3*e2*el+21*e0*e3*e2*e3
(2)*e0*el+14*el"(2)*еО*еЗ+6*е1Л(2)*е2*е1+14*е1Л(2)*е2*еЗ +^**е|3(£е2*е0*е1+28*е1*е2*е0*еЗ+12*е1*е2Л (2) *el+28*el*e2" (2) *еЗ * й#е1#еЗ*е0*е1+42*е1*еЗ*е0*еЗ+18*е1*еЗ*е2*е1+42*е1*еЗ*е2*еЗ + -#e2#el*e0*el+35*e2*el*e0*e3+15*e2*el*e2*el+35*e2*el*e2*e3 +30*е2^(2)*е0*е1+70*е2Л(2)*еО*еЗ+ЗО*е2*(3)*е1+70*е2Л(3)*е3 +45*е2*еЗ*е0*е1+105*е2*еЗ*е0*еЗ+45*е2*еЗ*е2*е1+105*е2*еЗ*е2*еЗ +^#е^#е1*е0*е1+14*еЗ*е1*е0*еЗ+6*еЗ*е1*е2*е1+14*еЗ*е1*е2*еЗ +12*еЗ*е2*е0*е1+28*еЗ*е2*е0*еЗ+12*еЗ*е2"(2)*е1+28*еЗ*е2"(2)*еЗ +18*63*(2)*еО*е!+42*еЗ"(2)*еО*еЗ+18*еЗ*(2)*е2*е1+42*еЗ"(2)*е2*еЗ
результат = 24*е0*е1 *е2*еЗ
Класс Symbolic и символьное дифференцирование
Первые интегралы
Рассмотрим автономную систему обыкновенных дифференциальных уравнений первого порядка
~ = V(u),
где и = … ,un) и Vj — гладкие функции переменных 乜匕叫 …,^n. Гладкая функция /(и) называется первым интегралом системы дифференциальных уравнений, если
/(u(切三e岡帚三e岡。心)=0.
Гадкая функция /(u(Z), i) называется явно зависимым от времени первым ^^^егралом системы дифференциальных уравнений, если
力(й“) = ^+t計%向力力=0.
Пример 1. Рассмотрим следующую систему дифференциальных уравнений:
(1и\血2
丁=u2й3,к=u1a3,к=u1й2・
atatat
M& показываем, что 厶,& 玄,з, где
厶(u)=请-磋,"(u) = W _ 若,/3(и)=並 _ W
Являют(:я первыми интегралами системы.
Пример 2. Рассмотрим модель Лоренца
出“du〉d〃3
石=-⑵1+g2,-^ = ^3+^-^,=〃]如一Е3
®т®тДля некоторых значений параметров бифуркации о, 6 0r система догвг явно зависимые от времени первые интегралы. Затем мы подставляем 口匸 жение
®т
®т
/(u(t)/)=禽 + Из) exp(2t)
в систему для нахождения условий на коэффициенты а, 丁, 6, при которых J(u(i), i) является первым интегралом.
Пример 1
// firstl.срр
#include <iostream.h>
#include "MSymbol.h"
#include "Vector.hn
void main()
{
int i, j, N=3;
Sum<int> ul("ul”,0), u2(”u2",0), u3("u3",0), term, sum=0;
Vector<Sum<int> > u(N), v(N),工(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[1]=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
:номер 3 =
0
a = °
:номер 1 =
-2*ul*u2*u3
г номер 2 二
0
г номер 3 =
2*u3*ul*u2
自 二 °
[номер 1 二
0
[номер 2 =
2*u2*ul*u3
[номер 3 =
~2*u3*ul*u2
qjieH Сумм; Член Член
Член Сумм;
Член Член
Член Сумма = О
Пример 2
// first2.cpp
#include <iostream.h>
#include nMSymbol.h"
#include "Vector.h"
void main()
{
int i;
Vector<Sum<int> > u(3), v(4);
Sum<int> term, sum, I, RI, R2, ul(”ul",0), u2("u2",0), u3("u3",0), t("t", s("s",0), b("b",O), r("r",0);
u[0]二 ul; u[l] = u2; u⑵=u3;
/ / Модель Лоренца
v[0] = s*u2- s*ul;
v[l] = -u2 - ul*u3 + r*ul;
v⑵ =ul*u2 - b*u3;v[3] = 1;
// Вставляемое выражение
I = (u2*u2 + u3*u3)*exp(2*t);
sum = 0;
for(i=0; i<3; i++) sum += v[i]*df(I,u[i]);
sum += v[3]*df(I,t);
8ut << " сумма = ” << sum << endl; cout << endl;
Rl = sum.coeff(u3,2);
Rl = Rl/(exp(2*t));
cout « "RI = " « RI « endl;
R2 = sum.coeff (ul,1); R2 = R2.coeff(u2,1);
R2 = R2/(exp(2*t));
8ut « "R2 = " « R2 « endl;
Результат
сумма = 2*r*ul*u2*exp(2*t)-2*Ъ*иЗЛ(2)*exp(2*t)+2*u3"(2)*exp(2*t)
Ri = -2*b+2
R2 = 2*r
требу、требу、Из полученных результатов следует, что для равенства нулю RI, R2 ется, чтобы b = 1 к г = 0.
требу、
требу、
Сферические гармоники
Сферические гармоники являются комплексными однозначными функция^и определенными на поверхности единичной сферы, т. е. функциями двух веще' ственных аргументов 0&8&тг, 0$0V 27r. Они определяются выражениями
匕 m(%«) := (-1)т2 РГ^о3е)е^, т > о,
47rM + 7几)!
где P^^w) — ассоциированные функции Лежандра, определяемые как
屮(喇:=(1-W2)H/2__PzHi
с I = 0,1, 2,... м т = —Z, —/ + 1,... , I. Для отрицательных значений m мы имеем
匕m(仇 0) = (-1产匕二",(40),
где звездочка (*) обозначает комплексное сопряжение. Сферические гармоники удовлетворяют соотношениям ортонормированности
匕院,(仇。)例”©</>)©= / 财 / de sm вУ^, (9, ф)У1т(9,ф),
где мы обозначаем dfl 三 sin 8d8dф к / О означает, что мы интегрируем по всему пространству угловых переменных (仇 0), а именно
Q = / d0 / 曲 sin 仇Jo Jo
Сферические гармоники образуют базис в гильбертовом пространстве 乙2($
где
S2 = {(雹1,±2,上3): 土; + 云 + 云=1}-
Первые несколько сферических гармоник суть следующие:
мО(仇0)=нО (仇 0)=мО(仇0)=нО (仇 0)=HiG。)=-HiG。)=-cos 8,cos 8,Нт(4。)Нт(4。)偿sinm, 'О7Г三sin %5 87r偿sinm, 'О7Г三sin %5 87r
мО(仇0)=
нО (仇 0)=
мО(仇0)=
нО (仇 0)=
HiG。)=-
HiG。)=-
cos 8,
cos 8,
Нт(4。)
Нт(4。)
偿sinm, 'О7Г
三sin %5 87r
偿sinm, 'О7Г
三sin %5 87r
Любая функция в гильбертовом пространстве 七2(夕)может быть записан^ " виде линейной комбинации сферических гармоник.
〃 spheric.срр
include <iostream.h>
★include "MSymbol.h"
#include "Rational.h" ^include "AssLegendre.h*'
Suni<Rational<int> > Р工(“Р1\0), К"Г',О);
int factoriaKint 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(m);
AssLegendre A(l,m,u);
a 二 A.current(); a.put(u,cos(w));
if(m>0 && m%2) a = -a;
b = sqrt(Rational<int>((2*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> > phi("phi",0), w("w",0), result, one(l);
for (i=0; i<=n; i++)
for(j=-i; jci; j++)
result 二 Y(i,j,phi,w);
result.put(cos(w)*cos(w),one-sin(w)*sin(w));
cout « "Y(” « i « "," << j << n) = " « result « endl;
}
cout << endl;
}
Результат
Y(0,0) = sqrt(l/4*P:T(-l))
Y(1,-1) = sin(w)*sqrt(3/8*PI"(-1))*exp(-I*phi)
Y(1,O) = cos(w)*sqrt(3/4*PI*(-1))
Y(1,1) = -sin(w)*sqrt(3/8*Р1Л(-1))*exp(I*phi)
Y(2,-2) = 3*sin(w)*(2)*sqrt(5/96*PI"(-l))*exp(-2*I*phi)
Y(2,-l) = 3*cos(w)*sin(w)*sqrt(5/24*PI*(-1))*exp(-I*phi)
Y(2,0) = sqrt(5/4*PI*(-l))-3/2*sin(w)*(2)*sqrt(5/4*PI^(-l))
Y(2,l) = -3*cos(w)*sin(w)*sqrt(5/24*PIA(-l))*exp(I*phi)
Y(2,2) = 3*sin(w)*(2)*sqrt(5/96*PI*(-1))*exp(2*I*phi)
Y(3,-3) = 15*sin(w)"(3)*sqrt (7/2880*PI"(-1))*exp(-3*I*phi)
Y(3,-2) = 15*cos(w)*sin(w)*(2)*sqrt(7/480*PI"(-1))*exp(-2*I*phi)
Y(3 ,-l) = 6*sin(w)*sqrt(7/48*PI"(-1))*exp(-I*phi)
-15/2*sin(w)^(3)*sqrt(7/48*PI"(-l))*exp(-I*phi)
Y(3,0) = cos(w)*sqrt(7/4*PI'(-1))
-5/2*cos(w)*sin(w)Л(2)*sqrt(7/4*PI"(-1))
Y(3,1) = -6*sin(w)*sqrt(7/48*PI"(-1))*exp(I*phi)
+15/2*sin(w)"(3)*sqrt(7/48*Р1Л(-1))*exp(I*phi)
Y(3,2) = 15*cos(w)*sin(w)"(2)*sqrt(7/480*PI*(-1))*exp(2*I*phi)
Y(3,3) = -15*sin(w) " (3) *sqrt (7/2880*P厂(T)) *exp(3*I*phi)
Небесная механика
В небесной механике фазовое пространство натянуто на 冗-ку динамических переменных щ для г = 1,... ^п. Уравнения движения небесной механики (т автономная система обыкновенных дифференциальных уравнений первого порядка) могут быть построены следующим образом. Пусть /比:R" —> R 工仪 k = 1,...,冗一1 являются гладкими функциями, тогда
ds _ 3(м6, a,.…,厶-i)(gS)
dt а(Н1, £2,〃n)'
где д{щ^ Ii,... , In~\)/d(ux ? u25 ■ • • ^n) обозначает якобиан. Следоват('льн1)- уравнения движения могут также быть записаны в виде (учитывая сог〃а1и("ш' о суммировании)
■^ 工 ^ijk...ldjl\ . - - &厶1_1,
где €ijk.,.i — универсальный символ Леви— Чивита и dj 三 d/duj. Доказак1-'1'^ ство того, что 厶,,Zn_i — первые интегралы системы (8.8), следующей- как (с учетом соглашения о суммировании)
d厶 дЦ dujdt duj dt '
2心"2心"dtdt(Jj/Jf 〃]..」_l(,l 厶)• • •(S…厶—l)(Jj/Jf 〃]..」_l(,l 厶)• • •(S…厶—l)0(〃 I)0(〃 I)
2心"
2心"
dt
dt
(Jj/Jf 〃]..」_l(,l 厶)• • •(S…厶—l)
(Jj/Jf 〃]..」_l(,l 厶)• • •(S…厶—l)
0(〃 I)
0(〃 I)
3-173-17/\ 7;1CC Syinbolic /I ('U.\"sc. 〃>"(“'「I"(/"/〃、J)C"IUf”)m;l"U(‘
3-17
3-17
двнженн”
г/и [
2,a厶
М НА
flll2
。八
0I-)
力k Dti-s
,九2 ()(12
dtii
ди 1 бу
3厶0厶
0厶 DI-2
力н ди->
О"? dll 1
по.'Н'чаем у])авн('нияпо.'Н'чаем у])авн('ния(口Унам n — 3 мы(口Унам n — 3 мы,как якобиан имеет дна равных(1т()/юца. он вырожденный. Если первьк1 〔'"©「卩㊀陋工 his/ihio'ioi многочленами, то /цтпамичрская система (8.8) алгебра- ":;СКН ио-'пюггы<} интегрируема. Для
по.'Н'чаем у])авн('ния
по.'Н'чаем у])авн('ния
(口Унам n — 3 мы
(口Унам n — 3 мы
Как npi^1('P- \п〃 рассматривж'м ш'бесную м('ханику с.'к'дуинцих н('рвых инт('- гралов ■
/1 二 Н[ + "2 + 〃2 [т 厶—t/l"2〃3‘
Программа нсно.чьзуе'т систс'му (8.8), итз6ь【построить 3x3 матрнн】” н выше-
лить их /цч-ерхпимнты:
/ Эи.; a/】 di-2 \
Oui dii\ Он”
0〃/ dl\ ()1
длх z = 1 2 3.
ди у ()fH 力匕
力勺 少八 0八
\ 6/:; /匕 0",3/
// nambu.срр
#include <iostream.h>
^include "MSymbol.h"
/include "Vector. h" ^include "Matrix.h"
void nambu(Vector<Sum<double> > I, Vector<Sum<double> > u, int n) {
^Q-trix<Sum<double> > J (n ,n);
for (i=0; 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 (]二0; j<n; j++) J[j][o]二 df(u[i], u[j]);
cout << "du(" << i << ")/dt 二"<< J.determinant() << endl;
}
void mainO
Sum<double> ul(”ul",0), u2("u2",0), u3("u3",0);
Vector<Sum<double> > I(2), u(3);
u[0] = ul; u[l] = u2; u[2] = u3;
l[0] = ul + u2 + u3; I [1] = ul * u2 * u3;
nambu(I,u,3);
Результат
du(0)/dt = ul*u2-ul*u3
du(l)/dt = -ul*u2+u2*u3
du(2)/dt = ul*u3-u2*u3
Разложение в ряд Тейлора дифференциальных уравнений
Рассмотрим обыкновенное дифференциальное уравнение первого порядка
где / — аналитическая функция переменных z к и. Разложение в ряд Тейлора дает аппроксимацию решения в окрестности 土 = 土。% решение дифференциального уравнения задается выражением
d2u dx2 (Ри dx3d2u dx2 (Ри dx3纥дхd2f纥дхd2fди dxди dx+ 2阪+ 2阪Производные могут быть получены последующим дифференцированием уравнений
d2u dx2 (Ри dx3
d2u dx2 (Ри dx3
纥
дх
d2f
纥
дх
d2f
ди dx
ди dx
+ 2阪
+ 2阪
Формулы для высших производных становятся очень усложненными. В &廿1?一 стве примера рассмотрим дифференциальное уравнение Риккати
〃2 + 土, U(0) = 1.
Это уравнение не имеет решения в элементарных функциях. Для его ре山е""' необходимо использовать функции Бесселя. Предположим, что решение м。?】'"。 разложить в ряд Тейлора,
d2u du cPu丽=2"/ + l,姦d2u du cPu丽=2"/ + l,姦网4cдмн все последующие производные
d2u du cPu
丽=2"/ + l,姦
d2u du cPu
丽=2"/ + l,姦
Н Производные могут быть найдены с использованием компьютерной алгеб-
Затем выбираем начальное значение и получаем разложение в ряд Тейлора Моть до порядка п. В примере 1 мы решаем дифференциальное уравнение >好眼丁”, используя ряды Тейлора вплоть до порядка п = 4 с начальным зна- [еяием 也(U) = L Решение имеет вид
/、134 з 17 4
U(N)= 1 + Е + -Х~ + -X + —3: + ….
Пример 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<=M; 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> > > u0(n), y(n);
u.depend(x);
u0[0] = u*u+x;
for(j=l; j<n; j++) uO[j] = df(uO[j-l],x);
cout « uO << endl;
// начальное условие u(0)=l x.set(O); u.set(l);
U0[0] = u0[0].value();
у[0] = и;
for (i=l; i<n; i++) y[i] = df(y[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].expand();
cout << uO << endl;
u = one;
cout << uO << endl;
// разложение в рад Тейлора
result = one;
for (i=0; i<n; i++)
result += Rational<int>(1,factorial(i+l))*u0[i]*power(x,i+1) cout << "u(x) = " << result << endl;
[u"(2)+x]
[2*u*df(u,x)+l]
[2*df(u,x)"(2)+2*u*df(df(u,x),x)]
[6*df(u,x)*df(df(u,x),x)+2*u*df(df(df(usx),x),x)]
[1]
[2*u+l]
[2+4*иЛ(2)+2*и]
[16*и+6+8*иЛ(3)+4*иЛ(2)]
[1] ⑶
[8]
[34]
u(x) = 1+х+3/2*х*(2)+4/3*хЛ(3)+17/12*хЛ(4)
В качестве другого примера рассмотрим нелинейную задачу Коши
丁 = * + пш — it2, где u(0) = 1.
cdu n du2 土 + 22 + N -г— — 2〃 —一 ,cdu n du2 土 + 22 + N -г— — 2〃 —一 ,1rочно просто, хотя и скучно, вычислить все производные:
cdu n du
2 土 + 22 + N -г— — 2〃 —一 ,
cdu n du
2 土 + 22 + N -г— — 2〃 —一 ,
d2u
2
=2 + 2 —
+ n——T ■
-2 —
—2u-
dx3
dx
dx2
\dx )
d^u
d2 H
du dru
d±4
dx1
dr J
dx dN?
曲:3
d2u
d2u dx2
В точке 北=0, и(£ = 0) = 1 имеем
(峪--1, \dxjx=o(峪--1, \dxjx=o=43,..,,=43,..,,
(峪--1, \dxjx=o
(峪--1, \dxjx=o
=43,..,,
=43,..,,
Окончательно зависимость начального значения и(х)Окончательно зависимость начального значения и(х)вблизи аг — О задаетсявблизи аг — О задается
Окончательно зависимость начального значения и(х)
Окончательно зависимость начального значения и(х)
вблизи аг — О задается
вблизи аг — О задается
м радом радо3 - 4 4 -2+3N4 -33 - 21±=3 - 4 4 -2+3N4 -33 - 21±=
м радо
м радо
3 - 4 4 -2
+
3N4 -33 - 2
1±=
3 - 4 4 -2
+
3N4 -33 - 2
1±=
В следующем примере мы воспроизводим процедуру вычисления, использующего нашу систему компьютерной алгебры.
Пример 2
// Тау1ог2.срр
tinclude <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:
fid main()
int i, j, n=4;
Hational<int> zero(0) , 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;
for (i=l; i<n; i++) uO[i] = df(uO[i-l],x);
cout << uO << endl;
// начальное условие u(0)=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>(1.factorial(i+1))*u0[i]*power(x,i+ cout << "u = " << result << endl;
Результат
[хЛ(2)+х*и-и^(2)]
[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)"(2)-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^(2)+x*u-2-6*u*(2)]
[7*x+31*u+x"(3)-x^(2)*u-8*x*u"(2)+12*u^(3)]
[-1]
[3]
[~8]
[43]
u = l-x+3/2*x"(2)-4/3*x"(3)+43/24*x"(4)
Коммутатор двух векторных полейКоммутатор двух векторных полейрассмотримМ первогорассмотримМ первого
Коммутатор двух векторных полей
Коммутатор двух векторных полей
рассмотримМ первого
рассмотримМ первого
автономную систему обыкновенных дифференциальных уравне- порядка
dx
每=心)
亿.Rn Т R н / = 1,2,…,тг. Мы считаем, что функции Vj аналитические. ^^ффереНциальным уравнением мы можем ассоциировать векторное поле
Пусть W — другое векторное поле
印N欧(X)診
出 определяем коммутатор V и W следующим образом:
Находим, что коммутатор обладает следующими свойствами:
RW] = ~\W,V]
忆[/ +印]=[%。]+ [匕印]
Тождество Якоби [[V,仞,U] + \\U,V],W] 4-[[叱 U],V] = О,
где U — аналитическое векторное поле в Rn. Аналитические векторные поля образуют алгебру
В следующей программе мы рассматриваем три векторных поля V, W, U и показываем, что они удовлетворяют трем свойствам, указанным выше.
〃 comm.срр
"include <iostream.h>
•include "MSymb'ol.h"
•include "Vector.h"
产耽 int n = 3;
ector<Sum<int> > x(n);
^ector<Sum<int>> commutator(Vector<Sum<int>> V,Vector<Sum<int>> W)
;nt j,k;
ector<Sum<int>> U(V.length(),0);
f°r (k=0; k<n; k++)
hr (j=0; j<n; j++)
12 7548
U [к] += 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⑵=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,0)
〃 [V,U+W] = [V,U] + [V,W]
Y = commutat or(V,U+W) - (commutator(V,U)+commutator(V,W)); cout << Y << endl;// вьшодится (0,0,0)
// тождество Якоби [[V,W],U] + [[U,V],W] + [[W,U],V] = 0
Y = commutator(comniutator(V,W) ,U)
+ commutator(commutator(U,V),W)
+ commutator(commutator(W,U),V);
cout << Y << endl;// вьшодится (0,0,0)
8.7.6. Производная Ли и векторное поле Киллинга
Пусть М есть риманово многообразие с метрическим тензорным полем
4
= Е Е gjk^dXj ® dxkj=l k=l
и V есть гладкое векторное поле, определенное в М. Векторное поле V называется векторным полем Киллинга^ если
Lv9 = 0, где Lyg обозначает производную Ли от g по направлению V. Производная линейна, т. е.
LviT^S^LvT^LyS, где Т н S суть (г, s) тензорные поля. Кроме того, производная Ли подчиняет^ правилу произведения
£^(Т 艺 S) = (£uS) ®T + S® (LVT\
§ есть (t, s) тензорное поле и Т есть (р, q) тензорное поле. Наконец, мы§ есть (t, s) тензорное поле и Т есть (р, q) тензорное поле. Наконец, мыв локальных координатахв локальных координатах4 d\^'Lvdxj = d(V\dxj) — dVj = £ 石^以,4 d\^'Lvdxj = d(V\dxj) — dVj = £ 石^以,即 У задается соотношением即 У задается соотношением4 к=屮住)乐.7=1J4 к=屮住)乐.7=1JОкончательно мы получаемгAz 8gjkdVij=lk=l !=1 ''7Окончательно мы получаемгAz 8gjkdVij=lk=l !=1 ''7dxj 0 dxk.dxj 0 dxk.В программе мы рассматриваем метрическое тензорное поле Гёделя g = dzi 0 dti — В exp(2£i)d±2 艺 dx2 + d±3 0 d±3 — d±4 ® d±4 -exp(xi)dx2 0 dt4 — exp(£i)dt4 ® dx2.В программе мы рассматриваем метрическое тензорное поле Гёделя g = dzi 0 dti — В exp(2£i)d±2 艺 dx2 + d±3 0 d±3 — d±4 ® d±4 -exp(xi)dx2 0 dt4 — exp(£i)dt4 ® dx2.Метрическое тензорное полеМетрическое тензорное полеможет быть также записано в матричной форме:может быть также записано в матричной форме:/ 1О о' О/ 1О о' ОО—ехр(2з;1)/2О ■-ехр(д)О—ехр(2з;1)/2О ■-ехр(д)О О1ОО О1ОО 、-exp(ci)О-1/О 、-exp(ci)О-1/
§ есть (t, s) тензорное поле и Т есть (р, q) тензорное поле. Наконец, мы
§ есть (t, s) тензорное поле и Т есть (р, q) тензорное поле. Наконец, мы
в локальных координатах
в локальных координатах
4 d\^'
Lvdxj = d(V\dxj) — dVj = £ 石^以,
4 d\^'
Lvdxj = d(V\dxj) — dVj = £ 石^以,
即 У задается соотношением
即 У задается соотношением
4 к=屮住)乐.7=1J
4 к=屮住)乐.7=1J
Окончательно мы получаем
гAz 8gjkdVi
j=lk=l !=1 ''7
Окончательно мы получаем
гAz 8gjkdVi
j=lk=l !=1 ''7
dxj 0 dxk.
dxj 0 dxk.
В программе мы рассматриваем метрическое тензорное поле Гёделя g = dzi 0 dti — В exp(2£i)d±2 艺 dx2 + d±3 0 d±3 — d±4 ® d±4 -exp(xi)dx2 0 dt4 — exp(£i)dt4 ® dx2.
В программе мы рассматриваем метрическое тензорное поле Гёделя g = dzi 0 dti — В exp(2£i)d±2 艺 dx2 + d±3 0 d±3 — d±4 ® d±4 -exp(xi)dx2 0 dt4 — exp(£i)dt4 ® dx2.
Метрическое тензорное поле
Метрическое тензорное поле
может быть также записано в матричной форме:
может быть также записано в матричной форме:
/ 1
О о
' О
/ 1
О о
' О
О
—ехр(2з;1)/2
О ■
-ехр(д)
О
—ехр(2з;1)/2
О ■
-ехр(д)
О О
1
О
О О
1
О
О 、
-exp(ci)О
-1/
О 、
-exp(ci)О
-1/
полеполеИы показываем, что векторное
поле
поле
丽—2exp(f)函丽—2exp(f)函V =犷2言 + 卜xp(—2g)- 纪ть поле Киллинга тензорного поля д. 〃 kill.срр
丽—2exp(f)函
丽—2exp(f)函
^include <iostream.h>
^include "Vector.h"
"include "Matrix.h"
"include "Rational.h"
"include "MSymbol.h"
*d main()
int j, k, 1, N=4;
^ational<int> zero(O), one(l), half(1,2), two(2);
^atrix<Sum<Rational<int> > > g(N,N), Lg(N,N,zero);
^ector<Sum<Rational<int> > > V(N), x(N);
sum<Rational<int> > xl("xl",0),x2("x2",0),x3("x3",0),x4("x4'*,0);
х[0] = xl; х[1] = х2; х⑵ =хЗ; х[3] = х4;
// метрика Гёделя
g[0] [0] = one; g[0] [1] = zero; g[0] [2] = zero; g[0] [3] = 2erQ<
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] = 2er0.
g[3] [0] = zero; g[3] [1] = -exp(xl) ; g[3] [2]=zero; g[3] [3]=-on^.
// векторное поле Киллинга для метрики Гёделя
V[0] = х2; V[1] = exp(-two*xl) - half*х2*х2;
V[2] = zero; V[3] = -two*exp(-xl);
// производная Ли
for (j=0; j《N; j++)
for (k=0; k<N; k++)
for (1=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] [1] *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
[1 0 0 0]
[0 -l/2*exp(2*xl) 0 -exp(xl)]
[0 0 10]
[0 -exp(xl) 0 -1]
векторное поле Киллинга для метрики Гёделя, [х2]
[ехр(-2*х1)-1/2*х2л(2)]
[0]
Е-2*ехр(-х1)]
производная Ли g по напрявлению V, Lg
[0 0 0
[0 0 0
0]
0]
[0
0
0
0]
[0
0
0
0]
Класс Matrix
Норма Гильберта—Шмидта
Ц严
4 и ^ —две произвольные матрицы n х п над R. Определим
(4,8) :=tr(ABT),
е " обозначает транспозицию В
и tr() обозначает след матрицы. Мы видим,
go
(ДВ) >о,
(4 в) = (в,闺,
(cA^B^^Bl
(4 + ^2f в) = (4, в) + (А2, в),
qeс С R・ Таким образом, (А, В) определяет скалярное произведение для пхп матриц над R,Скалярное произведение порождает норму, которая задается формулой
间:="^=' tf>"『.
Норма называется нормой Гильберта^Шмидта. Эти результаты могут быть распространены на случай бесконечного измерения при условии наложения ограничения
г=1 j = l
Если бесконечномерная матрица А удовлетворяет этому условию, то мы называем Л оператором Гильберта—Шмидта.
В следующей программе мы рассматриваем символьную 2x2 матрицу
4= Ь а
Норма реализуется в классе Matrix, который описан в гл. 6.
〃 Hilbert.срр
"include <iostream.h>
"include "Matrix.h"
'include "MatNorm.h"
'include "MSymbol.h"
竺 main()
St n = 2;
sum<double> a(ua" ,0), b(”b\0), yl;
^a-trix<Sum<double> > A(n,n);
А[0][0] = а; А[0] [1] = Ь;
А[1] [0] = b; А[1] [1] = а;
cout << n << "х" << п << " матрица A is \n" << А << endl;
yl = normH(A);
cout << "Норма Гильберта-Шмидта матрицы А есть" << yl << едд]. cout << endl;*
а = 2.0; b = 3.0;
cout << " Положим а = 11 << а << " и b = " << b << endl;
cout << 11 Норма Гильберта-Шмидта матрицы А есть "
<< yl << " или " << yl.value() << endl;
Результат
2x2 матрица А есть
[а Ь]
[Ь а]
Норма Гильберта-Шмидта матрицы А есть sqrt(2*аЛ(2)+2*ЬЛ(2))
Положим а = 2 и b = 3
Норма Гильберта-Шмвдта матрицы А есть sqrt(26) или 5.09902
8.82 Пара Лакса и гамильтонова система
Рассмотрим функцию Гамильтона (решетка Тода)
Я(р, q) - |(Pi +Р2 + W) + ехр® — 0)+ exp(g2 - 4з) + ехр(@з -史), где p, q — векторы импульса и координат, соответственно. Уравнения движения Гамильтона суть следующие:
dpjдН
dtdqj'
Вводя величины
出:=g exp j
dq? дН
Н =где ; = 1,2,3. at op-j
[豆(% -的+1)) , В := -Pj,
и зацикливая индексы (т. е. q《
三 Qi), мы находим, что уравнения ави*е"*'
Гамильтона принимают форму (с аз 二 0)
幣=出3一%+/箒=
一2* 空= 2(。"硝,竽=2期
。1卜2。2。1卜2。22. Теперь введем матрицы2. Теперь введем матрицыО
。1
卜2
。2
。1
卜2
。2
2. Теперь введем матрицы
2. Теперь введем матрицы
02
Ьз
一。1 О例一。1 О例матрицы L являются константами движения, следо-матрицы L являются константами движения, следо-^^^венные значения
一。1 О
例
一。1 О
例
матрицы L являются константами движения, следо-
матрицы L являются константами движения, следо-
•° ьНо и коэффициенты характеристического многочлена также являются в® стантами движения. Уравнения движения (8.9) могут быть переписаны в ;ре0став,ении Ло,кеа".
(8.10)(8.10)^ = [A,L](t).
(8.10)
(8.10)
Можно показать, что первые интегралы системы имеют вид
tr(Ln), где n = 1, 2,....
В следующей программе мы демонстрируем, что уравнения (8.9) и (8.10) эквивалентны. Во второй части программы мы показываем, что
tr(L) = ^1+62 + 63 и tr(Z/n) = b; + 质 + 皆 + 2(^2 + 2а|
—первые интегралы. Определитель матрицы
det(L)=Ь也力3 _瓦丸-Ь3a;
также является первым интегралом системы. Может ли det(L) выражаться в виде tr(Ln) для некоторого 冗〉0? Заметим, что det(L) — произведение собственных значений для L, a tr(L) — сумма собственных значений для L.
// Lax.ерр
ftinclude <iostream.h>
#include nMSymbol.h"
#include "Matrix.h"
^include "Rational.h"
void main()
^atrix<Sum<Rational<int> > > L(3,3), A(3,3), Lt(3,3); //Lt==dL/dt sum<Rational<int> > al("al",0), a2("a2",0),
bi(“bl“,O), Ь2("Ь2",0) , ЬЗСЬЗ'^О),
alt, a2t, bit, b2t, b3t;
^ational<int> zero(0);
TJ000000 rL г- Г- rL rL rL 1J 1J TJ TJ TJ TJ 0 12 0 12 rL rL rL rL rL rL L L L A A ATJ000000 rL г- Г- rL rL rL 1J 1J TJ TJ TJ TJ 0 12 0 12 rL rL rL rL rL rL L L L A A A=bl;L[0] [1]=al;L[l] [1]=zero; L [2] [1]н zero; A[0] [1]=al; A[l] [1]二 zero; A [23 [1]=bl;L[0] [1]=al;L[l] [1]=zero; L [2] [1]н zero; A[0] [1]=al; A[l] [1]二 zero; A [23 [1]=al; L [0] [2] = zero;
TJ
000000 rL г- Г- rL rL rL 1J 1J TJ TJ TJ TJ 0 12 0 12 rL rL rL rL rL rL L L L A A A
TJ
000000 rL г- Г- rL rL rL 1J 1J TJ TJ TJ TJ 0 12 0 12 rL rL rL rL rL rL L L L A A A
=bl;L[0] [1]
=al;L[l] [1]
=zero; L [2] [1]
н zero; A[0] [1]
=al; A[l] [1]
二 zero; A [23 [1]
=bl;L[0] [1]
=al;L[l] [1]
=zero; L [2] [1]
н zero; A[0] [1]
=al; A[l] [1]
二 zero; A [23 [1]
=b2; L [1] [2] = a2;
=a2; L[2] [2] = b3;
=-al; A [0] [2] = zero;
=zero; A [1] [2] = -a2;
二 a2: A[2] [2] = zero:
Lt = A*L - L*A; cout << "Lt =\n" << Lt << endl;
bit 工 Lt [0] [0] ; b2t = Lt [1] [1] ; b3t = Lt [2] [2];
alt = Lt [0] [1] ; a2t = Lt [1] [2];
cout << nbit = " << 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 << "工[0] = " « I[0] << endl;
I[1] = (L*L) .traceO ; cout << "I[1] = " << I[1] << endl;
I[2] = L.determinant(); cout << "工[2] = " << I[2] << endl; cout << endl;
for (i=0; i<n; i++)
result = blt*df(I[i],bl) + b2t*df(I [i],b2) + b3t*df(I [i],b3) + alt*df(I[i]sal) + a2t*df(I[i],a2);
cout << 'результат" << i+1 << " = " << result << endl;
Результат
Lt =
E-2*al"(2) -al*b2+bl*al 0]
[al*bl-b2*al 2*al"(2)-2*а2Л(2) -а2*ЬЗ+Ь2*а2]
[0 а2*Ь2-ЬЗ*а2 2*a2、(2)]
bit = -2*а「(2), b2t = 2*а1Л(2)-2*а2Л(2) , b3t = 2*а2Л(2)
alt 二-al*b2+bl*al, a2t = -а2*ЬЗ+Ъ2*а2
I[0] = bl+b2+b3
I[l] = ЫЛ (2)+2*al" (2)+b2" (2)+2*а2Л (2)+ЬЗЛ (2)
I [2] = Ы*Ь2*ЬЗ-Ы*а2Л (2) -а1Л(2) *ЪЗ
результат! = 0
результат2 = 0
результатЗ = 0
В этой программе Lt соответствует dL/dt^ a bit, b2t, b3t, alt, a2七 ‘°‘ ветствуют dbl/d& db2/dt, db3/d力 dal/dt^ da2/dt.
^еденные результаты показывают, что (8.9) эквивалентно (8.10) при ^енИИ dbl/dt с (0,0)-м, db2/dt с (1,1)-м, и т. д. элементами матрицы L. Да- ^卩®» езультат! = 0, результат2 = 0, результатЗ = 0 указывают на то, что :誡Н I⑵ являются первыми интегралами системы.
§8・3・ Аппроксимация Паде
а степенной ряд функции расходится, функция имеет особенности в не» опой области. Аппроксимация Паде — это полиномиальная дробь, которая одержит ту же информацию, что и степенной ряд на интервале, часто с ин. мацИей о существовании особенностей. В [N, М] аппроксимации Паде чи- ^йтель имеет степень М и знаменатель — степень N. Коэффициенты опреде- лдртся приравниванием членов с равными степенями х в следующем уравнении:
f(x)Q(x) 一 Р(с) = 4c^+n+i + 3z^+N+2 + с Q(0) = 1,
где 尸(土)/Q(£)есть [N, М] аппроксимация Паде для /(£)•
Предположим, что решение дифференциального уравнения можно выразить отрезком ряда Тейлора до к-го порядка:
/(с) = &о + diX + а^х2 4-4- ад;丿.
Тогда [N, М] аппроксимация Паде Р"(х), явно выраженная через коэффициенты 出,есть
&M — N+1&M — N+1dM-N+2dM-N+2^М+1^М+1det[N, М](£):=det[N, М](£):=ам• • • ам+Nj=N出—N工 工j=N出— N+l£ • • • 工片o。产ам• • • ам+Nj=N出—N工 工j=N出— N+l£ • • • 工片o。产&M—N+1&M—N+1&M-N+2&M-N+2detdetXNXN^N-l^N-l&m+n1&m+n1
&M — N+1
&M — N+1
dM-N+2
dM-N+2
^М+1
^М+1
det
[N, М](£):=
det
[N, М](£):=
ам• • • ам+N
j=N出—N工 工j=N出— N+l£ • • • 工片o。产
ам• • • ам+N
j=N出—N工 工j=N出— N+l£ • • • 工片o。产
&M—N+1
&M—N+1
&M-N+2
&M-N+2
det
det
XN
XN
^N-l
^N-l
&m+n1
&m+n1
'N + m + 1 = R. Отметим, что 出 三 0 при j < 0, и суммы, у которых началь- 11aH граница суммирования больше конечной границы суммирования, должны приравниваться нулю. Часто Р^(£)сходится к истинному решению диффе- ^нЦиального уравнения при ^, М оо даже при наличии расходимости ряда 、近пора решения. Обычно мы рассматриваем сходимость последовательности н0即:
'°"Ько'°"Ько{用(吟寻+1 (叫Р产(e),...}
'°"Ько
'°"Ько
Мсл<Мсл<при М = N + J, з J считается постоянной при ^ —> оо. Специальная
Мсл<
Мсл<
:еДовательность с J = 0 называется диагональной последовательностью.
В следующей программе мы вычисляем аппроксимацию Паде [1д] 也[3,3] для функции,№2] н
=sin⑵嗔号F…•
Дополнительно аппроксимация Паде [2,2] может быть вычислена как еле щая:"У10、
det2 2]=det2 2]=°200土2°200土2&2°3
det
2 2]=
det
2 2]=
°200土2
°200土2
°3°4
00£ +01 ④ 2a0+01£ +02于2
G1 a2 a3
det 02 03 04
X2 X 1
с a。= 0, a】=1с a。= 0, a】=1。2 = 0〉。3 = —1/6 и щ = 0. Мы определяем, что
с a。= 0, a】=1
с a。= 0, a】=1
⑵2]=⑵2]=££X21 +行X21 +行
⑵2]=
⑵2]=
£
£
X2
1 +行
X2
1 +行
// Pade.epp
#include <iostream.h>
#include "MSymbol.h"
#include "Rational.h1'
#include "Vector.h"
#include "Matrix.h1'
Sum<Rat i onal< int > >
Taylor(Sum<Rational<int> > u, Sum<Rational<int> > &x, int n) {
x.set(Rational<int>(0));
Sum<Rational<int> > series(u.value());
int j, fac = 1;
for (j=l; j<=n; j++)
{
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>> &x,int N,i口七") {
int i, j, k, N1 = N+l, Ml = M+l, n = M + Nl;
Ratiogl<int> zero(O);
g^Rational<int> > y, z;
^eC-tor<Sum<Rational<in.t> > > a(n);
^atrix<Sum<Rational<int> > > P(N1,N1), Q(N1,N1);
=:Taylor (f,x,n);
;or(i=°; i3; i++)武辺 =y.coeff (x,i);
for(i=0; i<N; i++)
for(j=0; j<Nl; j++)
к = M-N+i+j+l;
if (k >= 0) P[i] [j] = Q [i] [j] = a[k];
elseP[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.0 {
Sum<Rational<int> > x("x\0), f , g;
f = sin(x);
cout << Pade(f,x,1,1) << endl; // => x
cout « Pade(f ,x,2,2) « endl; // => T/6*x* (-1/6-丄/36*h2『(-1) cout << Pade(f,x,3,3) << endl;
// => (-7/2160*x+1589885/33068i6*x~3)*(-7/2160-7/43200*x-2)~(-i)
*9. Классы Array и Symbolic
8'9,L Псевдосферические поверхности и уравнения солитонов
Риделе 3.2.2 мы описали, как находить уравнение синуса Гордона для метри- 3 'кого тензорного поля. Была дана реализация с использованием REDUCE. и"®сь мы дадим реализацию, использующую SymbolicC++. В нашем примере
Объявляется как переменная, зависящая от х1 и х2. Оператор df О обозна-
чает дифференцирование. Так как члены типа cos2 (а;) и sin.2 (ж) появляю результате наших вычислений, мы используем тождествоR
cos2 (ж) + sin2 (ж)三 1
для упрощения выражения. Это делается с использованием функции pute、
MSymbol.h.'抬
// tensor.срр
#include <iostream.h>
#include "Matrix.h"
#include "Array.h"
#include "MSymbol.h"
void mainO{int a5b,m,n,c,K=2;Matrix<Sum<double>void mainO{int a5b,m,n,c,K=2;Matrix<Sum<double>>>Arrayl<Sum<double> Array2<Sum<double> Array3<Sum<double> Array4<Sum<double>Arrayl<Sum<double> Array2<Sum<double> Array3<Sum<double> Array4<Sum<double>> > > >> > > >Sum<double> u(”u",O),Sum<double> u(”u",O),g(K,K),gl(K,K); // обратная матрица к g х(К);Ricci(K,K), Riccii(K,K);ganunaCKjKjK);R(K,K,K,K);,sum, RR,g(K,K),gl(K,K); // обратная матрица к g х(К);Ricci(K,K), Riccii(K,K);ganunaCKjKjK);R(K,K,K,K);,sum, RR,
void mainO
{
int a5b,m,n,c,K=2;
Matrix<Sum<double>
void mainO
{
int a5b,m,n,c,K=2;
Matrix<Sum<double>
>
>
Arrayl<Sum<double> Array2<Sum<double> Array3<Sum<double> Array4<Sum<double>
Arrayl<Sum<double> Array2<Sum<double> Array3<Sum<double> Array4<Sum<double>
> > > >
> > > >
Sum<double> u(”u",O),
Sum<double> u(”u",O),
g(K,K),
gl(K,K); // обратная матрица к g х(К);
Ricci(K,K), Riccii(K,K);
ganunaCKjKjK);
R(K,K,K,K);
,sum, RR,
g(K,K),
gl(K,K); // обратная матрица к g х(К);
Ricci(K,K), Riccii(K,K);
ganunaCKjKjK);
R(K,K,K,K);
,sum, RR,
xi(“xl",O), x2("x2",0);
g[0] [0] = 1; g[0] [1] = cos(u);
g[l][0] = cos(u); g[l][1] = 1;
x[0] = xl; x[l] = x2;
gl 二 g.inverseO ;
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] Ы , x Ш ) + df (g[b] [n] ,x[m])
-df(g[m][n],x[b]));
gamma [a] [m] [n] = 0.5*sum;
cout << "gamma(H << a << "," << m << "," << n. << ")=
<< gamma [a] [m] [n.] << endl;
c°ut « endl;
f°r(a=O; a<K; a++)
for(m=0; m<K; m++)
for(n=0; n<K; n++)
for(b=0; b<K; b++)
R[a] [m] Ы M = df (gamma[a] [m] [Ъ] ,x [n])
-df (gammaW [m] Ы ,x[b]);
for(c=0; c<K; c++)
R[a] [m] [n] M += 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 (Ъ=0 ; b<K; b++) Ricci Ы Ы += R[b] [ml [b] [n.];
cout << "Ricci(“ << m << "," << n << ”)="
<< Ricci [m] Ш << end!;
cout << endl;
for(m=0; m<K; m++)
for (n=0; n.<K; n++)
Riccil[m][n] = 0;
for(b=0; Ъ<К; b++) Riccil [m] Ы += 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;
}
}
Результат
gamma(0,0,0) gamma(0,0,1) gamma(0,1,0) gamma(0,1,1) gamma(1,0,0) gamma(l,0,1) gamma(l,1,0) gamma(1,1,1)gamma(0,0,0) gamma(0,0,1) gamma(0,1,0) gamma(0,1,1) gamma(1,0,0) gamma(l,0,1) gamma(l,1,0) gamma(1,1,1)Ricci(0,0)=Ricci(0,1)=Ricci(1,0)=Ricci (1,1)=Ricci(0,0)=Ricci(0,1)=Ricci(1,0)=Ricci (1,1)=-cos-cos-cos-cos(u)*sin(u)Л(-1)*df(df(u,xO),xi) (u)*sin(u)"(-1)*df(df(u,xl),x0)(u)*sin(u)Л(-1)*df(df(u,xO),xi) (u)*sin(u)"(-1)*df(df(u,xl),x0)
gamma(0,0,0) gamma(0,0,1) gamma(0,1,0) gamma(0,1,1) gamma(1,0,0) gamma(l,0,1) gamma(l,1,0) gamma(1,1,1)
gamma(0,0,0) gamma(0,0,1) gamma(0,1,0) gamma(0,1,1) gamma(1,0,0) gamma(l,0,1) gamma(l,1,0) gamma(1,1,1)
Ricci(0,0)=
Ricci(0,1)=
Ricci(1,0)=
Ricci (1,1)=
Ricci(0,0)=
Ricci(0,1)=
Ricci(1,0)=
Ricci (1,1)=
-cos
-cos
-cos
-cos
(u)*sin(u)Л(-1)*df(df(u,xO),xi) (u)*sin(u)"(-1)*df(df(u,xl),x0)
(u)*sin(u)Л(-1)*df(df(u,xO),xi) (u)*sin(u)"(-1)*df(df(u,xl),x0)
-cos(u)*(-l+cos(u)"(2))*(-l)*df(u,x0)*sin(u) 0
0
(-1+cos (u) Л (2) ) * (-1) *df (u,xl)*sin.(u) (-“ cos (u)"(2))"(-l)*df (u,x0) *sin(u) 0 0
-cos(u)*(-l+cos(u)c(2))c(-2)*df(u,xi)*sin(u)
-sin(u)*(-l)*df(df(u,xO),xl)
-sin(u)"(-1)*df(df(u,xl),xO)
R = -2*sin(u)*(-1)*df(df(u,xl),x2)
8Л0. Классы Polynomial и Symbolic
8.10.1. Метод Пикара
Мы рассмотрим метод Пикара аппроксимации решения дифференциального уравнения
с начальным условием ?/(七0)二 уо, где / — аналитическая функция 七 и у. Интегрируя обе стороны уравнения, имеем:
у(ж) =y0+ / /(s,y(s))ds.
Теперь, начиная с уо, можно итеративно использовать эту формулу для достижения точного решения, если процесс сходится. Следующая аппроксимация задается выражением
%i+i ⑺二 Уо
+ /
f(s,yn(s))ds.
В примере аппроксимируются решения линейного дифференциального уравнения
dy ,
и нелинейного дифференциального уравнения
.2
используя пять и четыре шага метода Пикара. Начальные условия имеют £乳以' у(£ = 0) = 1. Мы также даем значение у{х = 2) для этих аппроксимаций- В
bj直 программе picard. срр мы используем класс Polinomial и интегриро- 1°Р с в этом классе. Во второй программе picardl. срр мы используем инте- кование в классе Symbol.
〃 picard.срр
^include "poly.h"
#inClude "rational.h"
int main (void)
^polyterm<R.ational<Verylong> > x("x");
polynomial<Rational<Verylong> > pic(x);
Rational<Verylong> zero("0"),one("l"),two("2");
int i;
cout « endl « " пять аппроксимаций для x+y : " << 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 << '' Аппрок симация в точке x=2 дает ” << pic(two) << endl;
cout << endl << ” пять аппроксимаций для х+у宀2 :" << 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;
8ut << ” Аппрокеимация в точке х=2 дает " << pic(two) << endl;
return 0:
РезУльтаты
:;j* ^проксимаци玄 для x+y :
%)х2х+(1)
"/6)х、з+х^2+х+(1)
(1/24)х^4+(1/3)хЛ3+х^2+х+(1)
(1/120) XС5+(工/12)X工+(2/3方С3+X宀2+X+(1)
(1/720)хЛ6+(1/60)хЛ5+(1/12)х^4+(1/3)хЛ3+хЛ2+х+(1)
Аппроксимация в точке х=2 дает 523/45
пять аппроксимаций для x+y^2 :
(1)
(1/2)хЛ2+х+(1)
(1/20)хЛ5+(1/4)хЛ4+(2/3)хЛ3+(3/2)хЛ2+х+(1)
(1/4400)x12+(1/400)x10+(31/2160)x^9+(29/480)x^8+(233/1260)x7 +(13/30)хЛ6+(49/60)хЛ5+(13/12)х"4+(4/3)хЛ3+(3/2)хЛ2+х+(1)
(2/445280000)x^23+(1/29360000)x^22+(607/997920000)x^21
+(943/190080000)хЛ20+(265897/8532216000)х^19
+(569963/3592512000)х"18+(5506583/8143027200)хЛ17
+(1963837/798336000)хЛ16+(1350761/174636000)х"15
+(2967707/139708800)х*14+(190159/3706560)х"13
+(1096099/9979200)x^12+(116621/554400)x^11
+(2195/6048)x^10+(6479/11340)x^9+(1657/2016)хЛ8
+(271/252)хЛ7+(13/10)хЛ6+(17/12)хЛ5+(17/12)хЛ4 +(4/3)хЛ3+(3/2)хЛ2+х+(1)
Аппрок симация в точке х=2 дает 295683385548145447/8696438938687Е
// spicard.срр
#include "MSymboLh"
#include "rational.h"
int main.(void)
Sum<double> x("x",0);
Sum<double> pic;
Sum<double> zero(0.0),one(L0),two(2.0);
int i;
cout<<en.dl<<h пять аппроксимаций для x+y : "<<endl;
x.set(O.O);
pic = one;
cout << pic << endl;
for(i=l;i<=5;i++)
{
// итегрируем и вычисляем в границах между х и zero pic 二 one+Int(x+pic,x)-Int(x+pic,x).value();
cout << pic << endl;
x・s‘x・s‘,et(2.0);,et(2.0);« н Аппроксимация в точке х=2 дает " << pic.value() << endl;« н Аппроксимация в точке х=2 дает " << pic.value() << endl;««X.set(0.0);X.set(0.0);endl << " пять аппроксимаций для х+уЛ2:" << endl;endl << " пять аппроксимаций для х+уЛ2:" << endl;pi,pi,с = one;с = one;cout « Pic « endl; for(i=l;i<=4;i++)cout « Pic « endl; for(i=l;i<=4;i++)
x・s‘
x・s‘
,et(2.0);
,et(2.0);
« н Аппроксимация в точке х=2 дает " << pic.value() << endl;
« н Аппроксимация в точке х=2 дает " << pic.value() << endl;
«
«
X.set(0.0);
X.set(0.0);
endl << " пять аппроксимаций для х+уЛ2:" << endl;
endl << " пять аппроксимаций для х+уЛ2:" << endl;
pi,
pi,
с = one;
с = one;
cout « Pic « endl; for(i=l;i<=4;i++)
cout « Pic « endl; for(i=l;i<=4;i++)
// итегрируем и вычисляем в границах между х и zero pic = one+Int(x+pic*pic3x)-Int(x+pic*pic,x).value(); cout « pic << endl;
}
X.set(2.0);
cout << " Аппроксимация в точке x=2 дает " << pic.value() << endl;
return 0;
}
Результаты
пять аппроксимаций для х+у:
1
1+0.5*х"(2)+х
l+xF2) +х+0.166667*х^ ⑶
1+хЛ(2)+х+0.333333*хЛ(3)+0.0416667*х^(4)
1+хЛ(2)+х+0.333333*хЛ (3) +0.0833333*х^ (4) +0.00833333*хХ5)
1+хЛ(2)+х+0.333333*хЛ(3)+0.0833333*хЛ(4)+0.0166667*хЛ(5)
+0.00138889*х"(6)
Аппроксимация в точке х=2 дает 11.6222
口且не аппроксимаций для х+у^2:
1
1+0.5*хс(2)+х
1+L 5*х~(2)+х+0.666667*х~(3)+0.05*х~(5)+0.25*х~(4)
1+1.5*х~(2)+х+1.33333*х~(3)+1.08333*х~(4)+0.433333*х~(6) +0.816667*х~(5)+0.0604167*х~(8)+0.184921*х~(7)+0.0143519*х~(9) +0.000227273*х~(11)+0.0025*х~(10)
1+1•5*х~(2)+х+1.33333*х~(3)+1.41667*х~(4)+1.41667*х~(5)
+L 0754*х~(7)+1.3*х~(6)+0.57134*х~(9)+0.821925*х~(8)
+0.36293*х"(10)+0.109838*х~(12)+0.210355*х~(11)+0.0212421*х'(14)
+0•0513034*х~(13)+0.00773472*х~(15)+0.00245991*х"(16)
+0-000158653*х*(18)+0.000676233*х~(17)+4.96107е-006*х~(20)
+3.11б39е-005*х~(19)+6.08265е-007*х~(21)+2.24578е-009*х~(23) +Б-16529е-008*х~(22)
Аппроксимацнн в точке х=2 дает 3400.05
8.11. Техника рядов Ли
Рассмотрим автономную систему обыкновенных дифференциальных уравнений
加=f(x), x(t = 0)三 x0,
где х =(七1,一. ,£几)丁 и хо — начальное значение в 力=0. Пусть fj (J 口 1,2,... , n) — аналитические функции, определенные в Rn. Рассмотрим аналитическое векторное поле
*岁(鳴•
Тогда решение задачи Коши при малых t может быть записано в виде
叼 ⑴=6Хр(УИ)巧 |х=х(0),
где ^ = 1,2,... , п. Продолжение экспоненциальных функций имеет результатом
俨
巧(方)=巧(。)+ 方丿(3 ) |х=х(0) ^ ^"丿(V(巧))1х=х(0)+
Этот метод называется техника рядов Ли. Рассмотрим модель Лоренца^ которая задается уравнениями
dxi ,也]
-3r = 0(忆2 -力), -Tr = 一£1*3 + ГХ1 -忆2, H = 2192 - 0^3, diatat
где Gj, b п г — положительные константы. Векторное поле задается выражением
V —0(£2 一力V —0(£2 一力+ (一砲窃+Tд -此+"1。2一城3)六.+ (一砲窃+Tд -此+"1。2一城3)六.
V —0(£2 一力
V —0(£2 一力
+ (一砲窃+Tд -此+"1。2一城3)六.
+ (一砲窃+Tд -此+"1。2一城3)六.
Отсюда решение системы есть
/俨\
£1(0) + W(£i)|x=x(o)+ — V(V(iri))|x=x(o)+ …
X⑴=I etVX2 = 此(0) + m(方2)|х=х(0) + 5u(u(£2)升х=х⑼ + …,
、 /%3 /К=К(0)2
\ 七3(0) + W(£3)|x=x(0) + yV(V(^3))|x=x(0) + …丿 где
U(£i)=。(攻-5),
U(£2)= -£2 - /£3 + т£1, u(£3)= 5此一6窃
V(V(^1))=一02(电-£1) +0(一此-£1£3 + t£1),
V(V(Ж2))=0(£2 - £1)(一七3 + T)+ £2 + £1窃一百1 - £1(£1£2 -城3),
V(V(^3))=0£2(£2 一 £1) + £1(-£2 一 £1£3 + ^1) 一 6(£1此一城3)。
Модель Лоренца обладает многими интересными свойствами. Дивергенция равна — (о + b + 1). Следовательно, каждый малый элемент объема стремится к нулю при 力 т оо, в случае независимости £,2:2,£з・ Однако это не означает, что каждый элемент объема сжимается в точку. Для некоторых значений параметра (например ст = 10, 6 = 8/3 и г = 28) мы находим, что близкие траектории расходятся экспоненциально. Система демонстрирует хаотическое поведение.
В следующей программе мы применяем технику рядов Ли в модели Лоренца. Здесь мы расширили экспоненциальную функцию вплоть до второго порядка. Во второй части программы мы повторяем решение рядов Ли с ст = 10, b 二 8/3 и г = 28.
// Lie.срр
#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;
Suni<double> xl ("xl" ,0) , x2("x2",0), x3("x3" ,0), t("tH ,0), s("s",0), b('*b",O) , r("r%0), plC'pl-'.O), p2("p2\0), p3("p3",0);
Vector<Sum<double> > xs(N), xg(N);
double half = 0.5;
x[0] = xl; x[l] = x2; x⑵=x3;
xg[0] == pl; xg[l] = p2; xg[2] = p3;
// модель Лоренца xt[0] = s*(x2-xl); xt [1] = ~x2 ~ xl*x3 + r*xl;
xt[2] = xl*x2 - b*x3;
// расширение рядов Тейлора до порядка 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(0.01); r.set(28.0); s.set(10.0); b.set(8.0/3.0);
xg[0].set(0.8); xg[l].set(0.8); xg[2].set(0.8);
for (j=0; j<100; j++)
for (i=0; i<N; i++) x[i].set(xg[i].nvalue());
for (i=0;i++)
xg[i].set(xs[i].nvalue());
cout << "x[" << i << ”] = " << xg[i].value() << endl;
cout << endl;
Результат
xs =
[xl+t*s*(x2-xl)+0.5*tc(2)*(-s"(2)*(x2-xl)+(-x2-xl*x3+r*xl)*s) x2 +t*(-x2-xl*x3+r*xl)
+0.5*t"(2)*(s*(x2-xl)*(~x3+r)+x2+xl*x3-r*xl-(xl*x2-b*x3)*xl) x3 +t*(xl*x2-b*x3)
+0.5*tc(2)*(s*(x2-xl)*x2+(-x2-xl*x3+r*xl)*xl-(xl*x2-b*x3)*b)]
x[0] = 0.81048
x[i] = 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
[0]二 0.950876
= 1.67493
;[2〕 = 0.759203
§ 12・ Спектры малых спиновых rpyim
рассмотримрассмотримспиновый оператор Гамильтона
рассмотрим
рассмотрим
зз
я = аe。3 з(3 +1) + Ье 内⑺,
гд6 & Ь ——вещественные константы и
Q(l) = ai^ I 0 I,S(2) = I 0 (7! 0 I,(71 (3) = / 艺 / 艺01,
где 6 являются матрицами Паули:где 6 являются матрицами Паули:
где 6 являются матрицами Паули:
где 6 являются матрицами Паули:
Здесь мы подразумеваем цикличность индекса, т. е.
о-з (4)三 <тз ⑴.
Матричное представление первого члена правой стороны есть диагональная матрица
з
^3。3(.7)озСУ + 1) = diag(3a, —а, —а, —а, —а, —а, —а, За).
Второй член приводит к недиагональным элементам. Симметрическая матрица размера 8x8 для Н приобретает вид
/За Ь Ь 0 Ь 000 \
b -a QbObQO
b 0 —а 60060
0bb —а 000b
6000 —а 660
ObOQb-aOb
OObObO-ab
\ 000 b 0 Ь ЬЗа/
спздующей программе мы вычисляем спиновый оператор Гамильтона сим-
в°льно. Мы символьно вычисляем след и детерминант матрицы. Затем мы
映"аем подстановку
1 % 1
Матрице и детерминанте, чтобы получить матрицу с числовыми величинами.
// spinS3.срр
#include #include #include #include #include#include #include #include #include #include<iostream.h>
#include #include #include #include #include
#include #include #include #include #include
<fstream.h> "MSymbol.h" ''Matrix, h" "Rational.h"
ofstream fout(nspinS3.dat");
Matrix<Sum<Rational<int> > > sigma(char coord, int inde
int i;
Matrix<Sum<Rational<int> > > 1(2,2);
I.identity();
Matrix<Sum<Ration.al<in.t> > > result, s(2,2);
Sum<Rational<in.t> > zero(O) , on.e(l);
if (coord == F)
s[0] [0] = zero; s[0] [1] = one;
s[l] [0] = one; s[1] [1] = zero;
else // ,z,
s[0] [0] = one; s[0] [1] = zero;
s [1] [0] = zero; s[l] [1] = -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,I);
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, T a, T b)
〔 i, size=pow(2,N);
i" ri;<T> result, part(size,size,T(0)), *sigmaX, *sigmaZ;
M曝aX 二 new Matrix<T>[N];
=sigma(,х\i ,N);=sigma(,х\i ,N);彳^1az 二 new Matrix<T>[N];
=sigma(,х\i ,N);
=sigma(,х\i ,N);
=sigma(Jz J,i,N);=sigma(Jz J,i,N);(i=0; i<N; i++) sigmaXti] :工(i=0; i<N; i++) sigmaZ [i] f0r(i=l; i<=N; i++) part += sigmaZ[i-1]*sigmaZ[i%N]; reSult = a * part;
=sigma(Jz J,i,N);
=sigma(Jz J,i,N);
part.filKTCO));
for (i=0; i<N; i++) part += sigmaXEi]; result += b * part;
delete 口 sigmaX; delete [] sigmaZ;
return result;
void main()
{
int N=3, size = pow(2,N);
Rational<int> c(l,2), d(l,3);
Sum<Rational<int> > a("a",0), b("b",0), p("p",0), det;
Matrix<Sum<R,ational<int> > > result, HI, I(size,size);
I.identityO ;
result = H(N,a,b);
font « result << endl;
font << "след 二"<< result.trace() << endl;
det = result.determinant();
font « "детерминант = " << det << endl;
font « endl;
// назначение числовых значений
а == с; Ъ = d;
foirt << " Положим = " << а << " и Ъ = " << d << endl;
font « endl;
'out << "Матрица становится:" << endl;
font << result << endl;
'out << " детерминант = " << det << endl;
Результат
[3*а b Ъ О Ъ 0 0 0] [Ъ -а О Ъ О Ъ О 0] [Ъ 0 -а Ъ О О Ъ 0] [ОbЪ-а О ООЪ]
[ЪОО0-а bЪ0]
[ОЪООЪ -аОЪ]
[ООЪОЪ 0 -аЪ]
[О О О b О b Ъ 3*а]
след = О
детерминант = 9*аЛ(8)-36*аЛ(6)*ЪЛ(2)+54*аЛ(4)*ЪЛ(4)-36*аЛ(2)*ЬЛ (6) +9*Ь*(8)
Положим а = 1/2 и b = 1/3
Матрица становится: [3/2 1/3 1/3 01/300 0]
[1/3 -1/2 0 1/3 0 1/3 О 0]
[1/3 0 -1/2 1/3 О 0 1/3 0]
[О 1/3[1/3 О[О 1/3[О 1/3[1/3 О[О 1/31/3 -1/2 000 1/3] О 0 -1/2 1/3 1/3 0] О 0 1/3 -1/2 0 1/3]
[О 1/3
[1/3 О
[О 1/3
[О 1/3
[1/3 О
[О 1/3
[О 0 1/3 0 1/3 0 -1/2 1/3] [ООО 1/3 0 1/3 1/3 3/2]
детерминант = 625/186624
8.13. Нелинейные отображения и хаотичное поведение
Мы рассматриваем отображение
±计1 = т(3弘 + 1)/(1 - g),亚+i = r(4xt + 1)软(1 一 %),
которое демонстрирует переход Рюэля—Такенса—Ньюхауза (Ruelle—Takens— Newhouse) к хаосу. Параметром бифуркации является г. Мы вычисляем вариационное уравнение символьно
М+1 = т(3勿 + 1)(1 - 2g)w 十 Згх^(1 - g)s, 仇+i = 3ryt(l 一 yt)ut + т(3生 + 1)(1 - 2如)% и затем повторяем эти четыре уравнения, используя тип данных double. На乳 больший показатель Ляпунова вычислен приближенно для г — 1.0834
Л в ^ln(|uT| + |依|),
? велико. Неподвижные точки отображения даются как решения уравне-
г (Зз/* +1)/* (1—/*) =,*,г (Зз/* +1)/* (1—/*) =,*,г(3у* + 1)У*(1 - 2/*) = У*-г(3у* + 1)У*(1 - 2/*) = У*-
г (Зз/* +1)/* (1—/*) =,*,
г (Зз/* +1)/* (1—/*) =,*,
г(3у* + 1)У*(1 - 2/*) = У*-
г(3у* + 1)У*(1 - 2/*) = У*-
祖垃 находим祖垃 находим若若—(—,4М — Зт + г), Зг—(—,4М — Зт + г), Згхп = 3(,4М - Зт +Згхп = 3(,4М - Зт +Згг — 1г — 1ггw - О,w - О,У1 - ^(-,4丁2 — 3「+ 城-員,4T2 - 3т + T),Уз = аУ4 =У1 - ^(-,4丁2 — 3「+ 城-員,4T2 - 3т + T),Уз = аУ4 =
祖垃 находим
祖垃 находим
若
若
—(—,4М — Зт + г), Зг
—(—,4М — Зт + г), Зг
хп = 3(,4М - Зт +Зг
хп = 3(,4М - Зт +Зг
г — 1
г — 1
г
г
w - О,
w - О,
У1 - ^(-,4丁2 — 3「+ 城-員,4T2 - 3т + T),
Уз = а
У4 =
У1 - ^(-,4丁2 — 3「+ 城-員,4T2 - 3т + T),
Уз = а
У4 =
灑=0,
Неподвижные точки (⑦力沙;)и (土力沙曰)существуют только при『> 3/4. В еле- дующей программе мы символьно вычисляем вариационные уравнения отображения, описанного выше. Затем мы вычисляем показатель Ляпунова с 『=1.0834 и начальными условиями
±о — 0.3, 加 = 0.4, 篁 ° — 0.5, vq = 0.6.
Из полученного результата мы видим, что показатель Ляпунова отображения равен приближенно 0.22.
// var.срр
#include <iostream.h>
#include <math. h>
«include "MSymbol.h"
template <class T>
return r*(3*y+l)*x*(1-x);
template <class T>
return, r* (3*x+l) *y* (1-y);
void main()
5t T, N = 1000;
double x2, y2, u2, v2;
Sum<double> x("x",0), xi(”xl\0), y("y",O), ylCyl" ,0), r("r",0), u("u",0) , ul("ulf,,0) , u("v\0), vl(Mvl",0);
xl = f(х,у,г); yi = g(x,y,r); cout << "xl =" cout « "yl ="// варигщионные ul = df(xl,x)*u vl = df(yi,y)*v cout << "ul 二" cout << "vl =" cout << endl;xl = f(х,у,г); yi = g(x,y,r); cout << "xl =" cout « "yl ="// варигщионные ul = df(xl,x)*u vl = df(yi,y)*v cout << "ul 二" cout << "vl =" cout << endl;< xl << endl;< yl << endl;уравнения+ df(xl,y)*v;+ df(yl,x)*u;< ul << endl;< vl << endl;< xl << endl;< yl << endl;уравнения+ df(xl,y)*v;+ df(yl,x)*u;< ul << endl;< vl << endl;
xl = f(х,у,г); yi = g(x,y,r); cout << "xl =" cout « "yl ="
// варигщионные ul = df(xl,x)*u vl = df(yi,y)*v cout << "ul 二" cout << "vl =" cout << endl;
xl = f(х,у,г); yi = g(x,y,r); cout << "xl =" cout « "yl ="
// варигщионные ul = df(xl,x)*u vl = df(yi,y)*v cout << "ul 二" cout << "vl =" cout << endl;
< xl << endl;
< yl << endl;
уравнения
+ df(xl,y)*v;
+ df(yl,x)*u;
< ul << endl;
< vl << endl;
< xl << endl;
< yl << endl;
уравнения
+ df(xl,y)*v;
+ df(yl,x)*u;
< ul << endl;
< vl << endl;
// Вычисление показателя Ляпунова // итерированием четырех уравнений.
// Начальные значения
x.set(0.3); y.set(0.4); r.set(1.0834);
u.set(0.5); v.set(0.6);
for (T=l; T<N; T++)
x2 = xl.nvalueO ;
y2 = yl.nvalueO ;
u2 二 ul.nvalueO ;
v2 = vl.nvalue();
x.set(x2); у.set(y2); u.set(u2); v.set(v2);
cout << ” Показателя Ляпунова для Т = " << Т << " is "
<< log(f abs(u.nvalue())+fabs(v.nvalue()))/Т « endl;
Результат
xl = 3*r*y*x-3*r*y*x"(2)+r*x-r*x"(2)
yl = 3*г*х*у-3*г*х*ул(2)+r*y-r*y"(2)
ul = 3*r*y*u-6*r*y*x*u+r*u-2*r*x*u+3*r*x*v-3*r*x,' (2) *v
vl = 3*r*x*v-6*r*x*y*v+r*v~2*r*y*v+3*r*y*u~3*r*y"(2)*u
Показатель Ляпунова для T =
1
есть
0.420853
Показатель Ляпунова для Т =
2
есть
0.113849
Показатель Ляпунова для Т =
3
есть
-0.2017
Показатель Ляпунова для Т =
4
есть
-0.0178022
Показатель Ляпунова для Т =
5
есть
0.1242
Показатель Ляпунова для Т =
6
есть
0.224871
^я3атель
Ляпунова
для
Т
=
994
есть
0.216136
110 а3атель
Ляпунова
Для
Т
=
995
есть
0.216531
Казатепв
Ляпунова
Для
Т
=
996
есть
0.215979
Указатель
Ляпунова
Для
Т
=
997
есть
0.215501
:<азатель
Ляпунова
Для
Т
=
998
есть
0.215698
Показатель
Ляпунова
ДОЯ
Т
=
999
есть
0.215851
814 Численно-символьное приложение
рассмотрим уравнениерассмотрим уравнение/0) = о,/0) = о,
рассмотрим уравнение
рассмотрим уравнение
/0) = о,
/0) = о,
г^е мы предполагаем, что функция / — по крайней мере дважды дифференцируема. Пусть I обозначает некоторый интервал, содержащий корень f. Метод приближения к корню / может быть описан следующим образом: берем касательную к кривой у = /(х) в текущей точке (хп,/(хп)), соответствующей текущему приближению хп корня. Пересечение ее с осью х дает следующую оценку Хпч-i корня. Наклон кривой g = /(х) в точке (хп,/(хп)) есть ?(£气).Касательная в этой точке имеет вид g 二 /'(⑦冗)* 土 + b・ Так как она проходит через точку (xn, f(xn)), то мы видим, что Ъ = /(£п) 一土п * 尸(£/;)• Следовательно, уравнение касательной прямой есть
У = f'^n) * 土 + /(±п) —* 尸(±n)・
Чтобы определить, где эта прямая пересекает ось х, мы приравниваем у = 0. Беря точку пересечения в качестве следующей оценки корня, мы имеем 0 二 fW) * £九+工 + f^n) - xn * 尸(土几).Из этого следует, что
Хп+1^Хп~7Ы'
В этом состоит метод Ньютона—Рафсона [15], который имеет следующую форму:
следующая оценка = текущая оценка + член коррекции.
"〃зн коррекции есть —/(хп)//^^) и он должен быть небольшим, когда хп близко к корню, если необходимо обеспечить сходимость. Это зависит от пове-
Аениа /'(с) вблизи корня и, в частности, мы столкнемся с трудностями, ког- 4a尸(叫и /⑺ имеют близкие корни. Метод Ньютона—Рафсона имеет вид ”九+i = g(±n) с g(z) := £ — /(£)//*£). Можно оценить порядок сходимости ме- 豊а. Дифференцируя это уравнение, получим д'(±) — (f (х)f"(х))/((fl(х))2). 4"兄 сходимости мы потребуем, чтобы
Для . ЙЬЦц(Для . ЙЬЦц(/("⑺(尸⑺产
Для . ЙЬЦц(
Для . ЙЬЦц(
всех х в некотором интервале I, содержащем корень. Так как /(а) = О, ^Указанное условие выполняется в корне х = а при условии, что /'(а) R О-
*Да> при условии, что д(±) непрерывно, вблизи корня должен существовать
интервал /, в котором это условие выполняется. Иногда можно встрети трудностью, когда интервал мал, поскольку начальное приближение「Ь" с браться в пределах этого интервала. Это обычно бывает, когда /⑵ и 捫口 имеют близкие корни, так как член коррекции обратно пропорционален (出) чине f (с).ве叽
В следующем примере мы используем описанный выше метод, чтобы 片- корень функции‘我
/(土)= 4n — cos (ж).
В программе мы сравниваем встроенный тип данных double и символьн р итерацию с численной подстановкой на каждом шаге. Последовательность СХо дится к величине 土 = 0.2426746806.
// Newton.срр
#include <iostream.h>
#include <iomanip.h> // для setprecision.0
#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, ff1, ft2, C;
ff = f(x);
ffl = 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;
х.set(u0);
cout << HC = " << С << endl;
ut 《< "IC(x=uO)I = ” << fabs(C.nvalue()) << endl; cout « endl;
〃 ======== Символьные вычисления =========
x.set(uO);
for (i=0; i<N; i++)
'у = (x - ff/ft 1).nvalueC);
x.set(y);
cOUt << "x = ” << x.nvalue() << endl;
f(x) = 4*x-cos(x)
f〉(x) = 4+sin(x)
f,,(x) = cos(x)
C 二 4*x*cos(x) *(4+sin(x) )*(-1) -cos(x)Л (2) *(4+sin.(x))" (-1) |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
Згиключение
В этой главе мы представили многие полезные приложения из математики и Физики, как, например, множество Мандельброта, первые интегралы, свертку векторных полей, технику рядов Ли и т. д. Мы продемонстрировали полезНость классов, которые мы разработали в предшествующих главах. Тем не ме~ нее читатели должны осознавать, что список представленных здесь приложе- HR* далек от полноты. Многие возможные области исследований будут иметь приложения с различными вариантами использования систем компьютерной ^гебры.
Глава 9
Лисп и компьютерная алгебра
Введение
Английское слово Lisp— сокращение для List Processing (обработка списков) обозначает машинный язык, который используется во многих прикладных программах искусственного интеллекта. Одно из его главных свойств состоит в том, что он позволяет легко манипулировать списками. Лисп был разработан в 1950-х годах Джоном Маккарти [32] и является одним из наиболее часто используемых языков для создания систем компьютерной алгебры. Reduce, Macsyma, Derive и Axiom базируются на Лиспе. Основные типы данных в Лиспе — атомы и точечные пары. Списки строятся из точечных пар. Большинство функций в Лиспе оперируют со списками. Кроме того, в Лиспе также доступны и арифметические операции.
В этой главе мы показываем, как система компьютерной алгебры может быть построена с использованием Лиспа. Она будет включать в себя такие возможности обработки многочленов, как упрощение и дифференцирование. В использовании функций мы будем полагаться строго на рекурсию. Имеются различные диалекты Лиспа. В этой главе мы используем переносимый стандартный Лисп (Portable Standard Lisp). Перечисленные здесь программы также выполняются в Коммон Лиспе (Common Lisp) с некоторыми небольшими изменениями. В переносимом стандартном Лиспе функция указывается ключевым словом (de ), тогда как в Коммон Лиспе функция указывается ключевым словом (defun. ). Комментарий в переносимом стандартном Лиспе записывается в виде
% Эта строка - комментарий в переносимом стандартном Лиспе тогда как строка комментария в Коммон Лиспе отмечается точкой с запятой (;):
;Это строка - комментарии в Коммон Лиспе
Функция (mapcan ) также реализована по-разному в Коммон Лиспе и в переносимом стандартном Лиспе.
Лисп описан в ряде превосходных учебников [2], [60]. В некоторых из них также обсуждается выполнение символьных манипуляций [8], [20], [38], [58卜
9 2 Вазовые функции Лиспа
ВНР;ВНР;йЯ в Лиспе называются S- выражениями (сокращение от символьного ^ажения). S-выражение есть либо атом, который записан как символ, на-
ВНР;
ВНР;
пример
а яблоко
part 2
〃ц6о точечная пара, записанная в виде
(s_i • s_2)
飙е s_l и s_2 обозначают произвольные S-выражения. Вот некоторые примеры точечных пар
(а . Ь)
(а • (Ъ1 • Ъ2))
((и . V) . (х . (у . z)))
Важное подмножество S-выражений — это списки, которые строятся по следующим правилам:
Атом есть список, если он —— атом nil.
Точечная пара (s_l . s_2) есть список, если s_2 есть список.
Атом nil рассматривается как пустой список. Следовательно, список в Лиспе —это последовательность из S-выражений. Следующие S-вьтражения:
nil
(apple . nil)
(а . (b . (с . nil)))
((apple . a2) . nil)
являются списками. Списки более удобно выражать в виде списка:
(s_l s_2 ... s_n)
叩只 n > 0. Это выражение является сокращением для
(s_l . (s_2 .(…(s_n . nil)…)))
Например? (а b с) есть сокращение для (а . (Ь . (с . nil))). Список (а) есть сокращение для (а . nil), а список ( ) (так называемый пустой список) есть сокращение для nil.
Отметим разницу между точечной парой (а . Ь) и двухкомпонентным диском (а Ъ), который может быть записан как (а . (b . nil)). ОчевидНо а и (а) не эквиваленты, так как последний есть сокращение для (а . nil).
•S-выражения являются значениями, управляемыми программами Лиспа. ^Днако программы в Лиспе также являются S-выражениями. Например, лиТералы в Лиспе записываются в форме
(quote S),
где S — некоторое S-выражение. Вместо (quote S) можно также написать) Синтаксически это всего лишь двухкомпонентный список. Первый ком^о^ атом quote, и второй S-выражение S. Семантически его значение (незави 。^飞 от любой формы) есть S-выражение S. Например, значение'""。
(quote apple)
есть атом apple, а значением
(quote (а . Ъ))
является точечная пара (а . Ъ). В Лиспе неупомянутый атом, используешь^ в выражении, является идентификатором^ кроме случаев, когда он является атомами nil или t, которые всегда обозначают сами себя.
В Лиспе изначально предусмотрено пять базовых операций для построения отбора и проверки значений 5-выражений'
cons сагсdratom null
Функция cons конструирует точечную пару из своих двух параметров. Например, значением
(cons (quote a) (quote b))
является (а . Ъ). Конечно, фактические параметры при вызове не должны быть литералами. Например, значением
(cons (quote a) (cons (quote b) nil))
также является (a . b). Функции car и cdr в качестве аргумента требуют точечную пару (s_l . s_2) и возвращают компоненты s」и s_2, соответственно. Например, значениями
(car (quote (а . Ъ))) и (cdr (quote (а . b)))
являются а и Ъ, соответственно. Далее мы рассмотрим результаты применения функций cons, саг и cdr к спискам аргументов. Значение
(cons (quote a) (quote (b с d)))
является списком (abed). Результатами применения саг и cdr к списку аргументов являются первый элемент списка и остальная часть списка, соответственно. Например, значения
(car (quote (а b с d))) и (cdr (quote (а b с d)))
есть а и (b с d), соответственно. Заметим также, что значения
(cons (quote a) nil), (car (quote (a))), (cdr (quote (a)))
есть (a) , а, и nil, соответственно. Оставшиеся две базовые функции в Лисп^ являются предикатами, результатами которых являются атомы —nil (пре^ ставляющий ложь) и t (представляющий истину). Значение предиката
(atom Е)
еСЛи S-выражение Е является атомом, и nil, если это — точечная пара, с'' икат null имеет один параметр. Он возвращает t7 если его аргумент пуст, 口吟 возвращает nil. Например
(null nil)
звратает значение t. Функция greaterp имеет один или несколько числовых в°;ументов и возвращает t, если они упорядочены по убыванию. В противном 普产1c возвращается nil. Например,
(greaterp 94)% возвращаемое значение: t
функция zerop берет числовой аргумент и возвращает t, если его значение есть нуль. В противном случае возвращается nil. Например,
(zerop 1)(zerop 1)% возвращаемое значение: nil
(zerop 1)
(zerop 1)
дрИфметическая функция plus складывает свои аргументы. Например, (plus 2 4 -7)% возвращаемое значение: -1
Арифметическая функция times перемножает свои аргументы. Например, (times 4567)% возвращаемое значение: 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 ?(1 2 3 4 5 xx)) %возвращает значение: (12345 xx)
(length.list L)% возвращает значение: 6
УНкция map с ап имеет два аргумента: функцию и список. Она применяет Функцию ко всем элементам списка. Функция, используемая здесь в качестве ^гУМента, должна возвращать результатом список, а результаты применения ^Ункции после разрушения структуры соединяются в единый список. Напри-
Р,в Коммон Лиспе мы имеем
(mapcan *cdr* ((b с) (de)))% возвращает значение: (b с е)
В переносимом стандартном Лиспе это выражается как
(mapcan J ((b с) (d е)) Kdr % возвращает значение: (b с е)
Многие функции в Лиспе могут быть определены в терминах других фуНк Например, сааг может быть определена в терминах саг. Естественно с?“" сить, имеется ли наименьший набор примитивов, достаточный для реализат^0' языка? Фактически такого «лучшего» минимального набора примитивов 口 имеется; все зависит от реализации. Один из возможных наборов примитиве6 мог бы включать car, cdr и cons для манипуляции S-выражениями, qUote atom, read, print, eq для равенства, cond для условных выражений, setq 内口; назначения и defun для определений.
Примеры символьных вычислений
Многочлены
В этом разделе мы показываем, как складываются и умножаются многочлены [12] вида
Сначала мы должны дать представление для многочлена р, используя список точечных пар. Первый элемент точечной пары — экспонента, а второй элемент——коэффициент. Таким образом, саг точечной пары — экспонента, и cdr точечной пары — коэффициент, например,
(саг М3 . 5)) %возвращает значение: 3
(cdr〉(3 . 5)) %возвращает значение: 5
Рассмотрите, например, многочлен
р(£)= За;2 + 7z + 1.
Представление в виде списка точечных пар имеет вид
((2 . 3) (1 . 7) (0 . 1))
Рассмотрим другой пример: многочлен За;2 + 2 имеет представление
((2 . 3) (0 . 2))
Используя это представление, многочлен 0 может быть представлен как 口订 или ((0 . 0)), тогда как многочлен 1 представлен в виде ((0 . 1)).
Ниже мы даем реализацию для сложения и умножения двух многочлено® [12]. Здесь используется рекурсия (т. е. функция вызывает саму себя). Е^" многочлен Р имеет m членов, и многочлен Q имеет п членов, то время вЫчиС ления (которое является числом операций Лиспа) для сложения ограни"。"。 величиной О(т + тг), а для умножения — величиной 0(m2n). Грубо гов°?^ для выполнения умножения мы должны рассортировать члены произвел611
чтОбы они располагались в убывающем порядке, и, используя функцию 『"К' рния, заменить все члены с одинаковыми экспонентами на один в соответ- ”° 10mем месте. Конечно, использование лучших методов сортировки (типа ”纟Р°立 сортировки) дает более эффективный алгоритм умножения с оцен- 6^(9(тп^ log(m))- Но в большинстве систем используется алгоритм, подобный Даяной процедуре.
% poly•s'
(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))))))
% Применение функций
力 сложения и умножения
(add >((2 . 3) (1 . 7) (add >((0 . 0)),((3 . (multiply > ((з . 3) (0 (multiply,((з . 3) (0(add >((2 . 3) (1 . 7) (add >((0 . 0)),((3 . (multiply > ((з . 3) (0 (multiply,((з . 3) (0(0 . 1)) ,((2 . 4) (0 . 2)))
(add >((2 . 3) (1 . 7) (add >((0 . 0)),((3 . (multiply > ((з . 3) (0 (multiply,((з . 3) (0
(add >((2 . 3) (1 . 7) (add >((0 . 0)),((3 . (multiply > ((з . 3) (0 (multiply,((з . 3) (0
4) (1 . 2) (0 . 7)))
■ 2)) ,((2 . 4) (1 . 1)))
■ 2)) '((0 . 0)))
В первом примере мы складываем два многочлена
(3d + 7n + 1) + (4d + 2) = 7/ + 7片 + 3. г*
0 втором примере мы складываем два многочлена
0 + (4ж3 + 2а + 7) = 4ж3 + 2片 + 7.
третьем примере мы перемножаем два многочлена
(3d + 2) * (4d + £)= 12/ + зх4 + 8d + 2w.
'етвертом примере мы перемножаем два многочлена
(3d + 2) * 0 = 0.
вЫводе последнего примера имеем ((3 . 0) (0 . 0)), что является мно- "бном 0 * 勺3 + ° * £°0 Он упрощается до 0.
Упрощения
Здесь мы показываем, как упрощение может быть осуществлено в Лиспе к предполагаем, что математическое выражение дается в префиксной 冗。由@ "瓦
Это означает, что в выражении операторы располагаются перед их оп ^' дами.%н、
В simpl. si мы реализуем правила
exp(0)
-> 1
log(l)
-> 0
ain(0)
-> 0
cos(0)
-> 1
arcsin(O)
-> 0
arctan(O)
-> 0
sinh(0)
-> 0
cosh(0)
-> 1
+ х
-О
-> О
В основном упрощение производится путем сравнения элементов выражения с элементами нашего списка. Если имеется соответствие, то заменяем выражение упрощенной формой. Отметим, что
(simp-unary 5(plus (plus х))) (simp-unary ?(minus (plus x))) (simp-unary ,(plus (exp 0))) (simp-unary *(exp (sin 0))) (simp-unary *(log (exp (sin 0))))
не упрощены полностью. Это указывает на то, что необходима рекурсия упрощения для применения на различных уровнях. Кроме того, мы видим, что
(simp-unary J (plus х у))
дает неправильный ответ, и
(simp-unary Jx)
(simp-unary J(x))
выдадут сообщения об ошибках! Это побуждает нас уделить дополнительно6 внимание числу и типам параметров для функции simp-unary. В следук>Ше^ попытке (simp2.sl) мы пробуем преодолеть эти проблемы.
% simpl.si
(de simp-unary (f) (prog (op opd) (setq op (car f)) (setq opd (cadr f)) (return (cond
9.3. Примеры символьных
вычислена
<389
((eq op ?plus)
opd)
%
+ X=> X
((and (eq op Jminus) (zerop opd))
0)
%
-0=> 0
((and (eq op ,exp)(zerop opd))
1)
%
ехр(0)=> 1
((and (eq op ,log)(опер opd))
0)
%
log(l)=> 0
((and (eq op Fin)(zerop opd))
0)
%
sin(0)=> 0
((and (eq op Jcos)(zerop opd))
1)
%
cos(0)=> 1
((and (eq op ’arcsin) (zerop opd))
0)
%
arcsin(0) => 0
((and (eq op 〉arctan) (zerop opd))
0)
%
arctan(0) => 0
((and (eq op 1 sinh) (zerop opd))
0)
%
sinh(0) => 0
((and (eq op Kosh) (zerop opd)) (t (list op opd)))))
)
% приложения упрощений
(simp-unary ; (plus x))Z=>x
(simp-unary '(exp 0))%=>1
(simp-unary J (log 1))%=>。
(simp-uiiary '(cosh 0))%=>1
(simp-unary '(exp 1))%=>(exp 1)
(simp-unary,(minus x))% => (minus x)
(simp-unary J (minus 0))% => 0
1)
%
cosh(0)=> 1
(simp-unary J (plus (plus x))) % => (plus
X)
<не упрощается!
(simp-unary ?(minus (plus x)))Z => (minus(plus
x))<не упрощается!
(simp-unary ? (plus (exp 0))) % => (exp ।
0)
<не упрощается!
(simp-unary ?(exp (sin 0)))% => (exp
(simp-unary J(log (exp (sin 0))))
(sin 0))
<не упрощается!
% => (log (exp
(sin 0))))
<не упрощается!
(simp-unary ? х)
% Сделана попытка применить % который не есть пара <
саг к 'x', ОШИБКА !
(simp-unary J (х))
% Сделана попытка применить
саг к 'nil
% которое не есть пара <
ОШИБКА!
(simp-unary ,(plus х у))
% 二>х<
НЕВЕРНО!
программе simp2.si мы успешно преодолеваем все проблемы, существу- 々"е в предыдущей версии. В этой версии упрощение может применяться к
атомам, для которых возвращается первоначальное выражение;
'口искам только с одним элементом, для которых мы применяем функцию 'imp с тем же аргументом;
• спискам с двумя элементами, для которых мы применяем фуНК]
simpc-unary с первым аргументом, являющимся оператором, и являющимся операндом;
• спискам с больше чем двумя элементами, для которых сообщение
дРУгим)
ется cannot .simplify, потому что такие выражения могут быть только бинарным оператором.
晔 ат&.
Упрощен^
Мы сформировали еще один уровень над функцией simp-unary. Нов функция simp играет важную роль в обработке величин, поставляемых 口口口卜 зователями. Она проверяет как типы, так и количество параметров. Действие ее различно для каждого отдельного случая.
В функции simp-unary большинство инструкций те же самые, как и в пре дыдущей программе simpl. si. Мы изменили только одну инструкцию (строка 4) в этой функции, чтобы сделать ее рекурсивной:
(setq opd (simp (cadr f)))
Эта инструкция применяет упрощение ко второму аргументу функции, тем самым позволяя применять упрощение к операндам различного уровня.
Заметим, что инструкция
(simp J (minus (minus х)))
не упрощена, потому что программа не учитывает взаимодействие между различными уровнями выражения, т. е. операторы minus не взаимодействуют и взаимно не уничтожают друг друга. Кроме того, мы видим, что инструкция
(simp *(plus х у))
выдает сообщение cannot_simplify. Это связано с тем, что мы пока не реализовали бинарных операторов. В следующей программе simp3. si мы рассматриваем упрощение бинарных операторов, таких, как plus, minus, times, quotent и power.
% simp2.si
(de simp (f)
(cond
((atom f) f)%f является атомом
((null (cdr f)) (simp (car f))) % f имеет только один элемент
((null (cddr f)) (simp-unary f)) % f имеет два элемента
(t (quote ccmnot_simplifу)) ))% f имеет более двух элементов
(de simp-unary (f)
(prog (op opd)% локальные переменные: op, 0Pd
(setq op (car f))
(setq opd (simp (cadr f)))
(return (cond
9.3. Примеры символьных вычислений
391
((eq op ;plus)opd) % + x=>
X
((and (eq op ’minus) (zerop opd)) 0)% - 0=>
0
((and (eq op ’exp)(zerop opd)) 1) Z exp(0)=>
1
((and (eq op ;log)(опер opd))0)% log(l)=>
0
((and (eq op 'sin)(zerop opd)) 0)% sin(0)=>
0
((and (eq op ’cos)(zerop opd)) 1)% cos(0)=>
1
((and (eq op Bresin) (zerop opd)) 0)% arcsin(O)=>
0
((and (eq op ?arctan) (zerop opd))0) Z arctan(0)=>
0
((and (eq op 'sinh)(zerop opd)) 0)7. sinh(0) =>
0
((and (eq op Kosh)(zerop opd)) 1)% cosh(0) =>
(t (list op opd)))))
)
% Приложения упрощений
(simp,x)%=>x
(simp'(x))7.=>x
(simpJ (plus x))%=>x
(simp1 (plus (plus x)))%=>x
(simpJ (plus (minus x)))%=>(minusx)
(simp? (plus (minus 0)))%=>0
(simp; (minus (plus x)))%=>(minusx)
(simpJ (minus (sin 0)))%=>0
(simp? (plus (exp 0)))%=>1
(simp,(minus (exp 0)))%=>(minus1)
(simp1(plus (log 1)))%=>0
(simp?(plus (cosh 0)))%=>1
(simp?(exp (sin 0)))%=>1
(simp?(log (exp (sin 0))))%=>0
(simp,(minus (minus x)))%=>(minus(minus x))
(simp J(plus x y))% => cannot_simplify
1
В программе simp3. si на основе одноместных операторов упрощения мы Реализуем следующие упрощающие бинарные операторы:
Plus minus times quotient power
В Функции simp мы изменяем инструкцию в строке 6
(t (quote caimot_simplifу))
«а
(t (simp-binary f))
。項 инструкция просто означает, что если аргумент содержит больше двух ^ментов, то применяется бинарный оператор упрощения.
В новой функции simp-binary мы рассматриваем следующие упрощеНй • операция х + у:
если х = 0, возвращается у, если у = 0, возвращается х,
• операция х — у:
если х = 0,
если у = 0, возвращается 0, иначе возвращается -у, если у = 0, возвращается х, если х = у, возвращается 0,
• операция х * у:
если х = 0 или у = 0, возвращается 0,
если х = 1, возвращается у,
если у = 1, возвращается х,
• операция х/у:
если х = 0, возвращается 0, если у = 0, возвращается infinity, если у = 1, возвращается х,
• операция ху:
если х = 0, возвращается 0,
если у = 0 или х = 1, возвращается 1, если у = 1, возвращается х.
% simp3.si
(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)
opd
(prog (op opd)
(setq op (car f))
% локсшьные переменные: op,
(setq opd (simp (cadr f))) (return (cond
((eq op Jplus)
opd) % + x=>
X
((and (eq op ?minus)
(zerop opd)) 0)% - 0=>
0
((and (eq op Jexp)
(zerop opd)) 1)% exp(0)=>
1
((and (eq op Tog)
(опер opd))0)% log(l)=>
0
(eq op J sin)(zerop opd))0)% sin(O)=> 0(eq op ? cos)(zerop opd))1)力 cos(O)=> 1(eq op J arcsin)(zerop opd))0)% arcsin(0)=> 0(eq op ?arctan )(zerop opd))0)% arctan(O)=> 0(eq op J sinh)(zerop opd))0)% sinh(O)=> 0(eq op 〉cosh)(zerop opd))1)% cosh(O)=> 1(eq op J sin)(zerop opd))0)% sin(O)=> 0(eq op ? cos)(zerop opd))1)力 cos(O)=> 1(eq op J arcsin)(zerop opd))0)% arcsin(0)=> 0(eq op ?arctan )(zerop opd))0)% arctan(O)=> 0(eq op J sinh)(zerop opd))0)% sinh(O)=> 0(eq op 〉cosh)(zerop opd))1)% cosh(O)=> 1((and ((and ((and ((and ((and ((and((and ((and ((and ((and ((and ((and(t (list op opd))))))(t (list op opd))))))(de simp-binary (f)(prog (op opdl opd2)(setq op (car f))(setq opdl (simp (cadr f)))(setq opd2 (simp (caddr f)))(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))))(return (cond((and (eq op ’plus)(cond ((zerop opdl) opd2)((zerop opd2) opdl))))операция : х + уоперация : х + уесли х=0, если у=0,если х=0, если у=0,возвращаем у возвращаем хвозвращаем у возвращаем х((and (eq op Jminus) (cond ((zerop opdl)(cond ((zerop opd2) 0)((and (eq op Jminus) (cond ((zerop opdl)(cond ((zerop opd2) 0)операция: если x=0, если y=0,операция: если x=0, если y=0,X - уX - увозвращаем Овозвращаем О(t (list *minus 0Pd2))))力 иначе возвращаем -у(t (list *minus 0Pd2))))力 иначе возвращаем -у((zerop 0Pd2) opdl)((eq opdl opd2) 0))))((zerop 0Pd2) opdl)((eq opdl opd2) 0))))% если y=0, % если x=y,% если y=0, % если x=y,возвращаем х возвращаем Овозвращаем х возвращаем О
(eq op J sin)
(zerop opd))
0)
% sin(O)
=> 0
(eq op ? cos)
(zerop opd))
1)
力 cos(O)
=> 1
(eq op J arcsin)
(zerop opd))
0)
% arcsin(0)
=> 0
(eq op ?arctan )
(zerop opd))
0)
% arctan(O)
=> 0
(eq op J sinh)
(zerop opd))
0)
% sinh(O)
=> 0
(eq op 〉cosh)
(zerop opd))
1)
% cosh(O)
=> 1
(eq op J sin)
(zerop opd))
0)
% sin(O)
=> 0
(eq op ? cos)
(zerop opd))
1)
力 cos(O)
=> 1
(eq op J arcsin)
(zerop opd))
0)
% arcsin(0)
=> 0
(eq op ?arctan )
(zerop opd))
0)
% arctan(O)
=> 0
(eq op J sinh)
(zerop opd))
0)
% sinh(O)
=> 0
(eq op 〉cosh)
(zerop opd))
1)
% cosh(O)
=> 1
((and ((and ((and ((and ((and ((and
((and ((and ((and ((and ((and ((and
(t (list op opd)))))
)
(t (list op opd)))))
)
(de simp-binary (f)
(prog (op opdl opd2)
(setq op (car f))
(setq opdl (simp (cadr f)))
(setq opd2 (simp (caddr f)))
(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))))
(return (cond
((and (eq op ’plus)
(cond ((zerop opdl) opd2)
((zerop opd2) opdl))))
операция : х + у
операция : х + у
если х=0, если у=0,
если х=0, если у=0,
возвращаем у возвращаем х
возвращаем у возвращаем х
((and (eq op Jminus) (cond ((zerop opdl)
(cond ((zerop opd2) 0)
((and (eq op Jminus) (cond ((zerop opdl)
(cond ((zerop opd2) 0)
операция: если x=0, если y=0,
операция: если x=0, если y=0,
X - у
X - у
возвращаем О
возвращаем О
(t (list *minus 0Pd2))))力 иначе возвращаем -у
(t (list *minus 0Pd2))))力 иначе возвращаем -у
((zerop 0Pd2) opdl)
((eq opdl opd2) 0))))
((zerop 0Pd2) opdl)
((eq opdl opd2) 0))))
% если y=0, % если x=y,
% если y=0, % если x=y,
возвращаем х возвращаем О
возвращаем х возвращаем О
((and (eq op Jtimes)% операция: x * у
(cond ((or (zerop opdl) (zerop opd2)) 0) % если x=0 или
% возвращаем О
((опер opdl) opd2)% если x=l, возвращаем у
((опер opd2) opdl))))% если у=1, возвращаем х
% операция: % если x=0, % если y=0, % если y=l,% операция: % если x=0, % если y=0, % если y=l,x / увозвращаем Овозвращаем infinity возвращаем хx / увозвращаем Овозвращаем infinity возвращаем х((and (eq op Quotient) (cond ((zerop opdl) 0)
% операция: % если x=0, % если y=0, % если y=l,
% операция: % если x=0, % если y=0, % если y=l,
x / у
возвращаем О
возвращаем infinity возвращаем х
x / у
возвращаем О
возвращаем infinity возвращаем х
((zerop opd2) J infinity)
((опер opd2) opdl))))
((and (eq op Jpower) (cond ((zerop opdl) 0)
% операция: x~y
% если x=0, возвращаем 0
((or (zerop opd2)(опер opdl))1)%если у=0 или х=1, возвращаем 1 ((опер opd2) opdl)))) % если у=1, возвращаем х
(t (list op opdl 0Pd2)))))
(simp J(plus (plus x)))
% => X
(simp ?(plus (minus x)))
% => (minus x)
(simp Л (minus (minus x)))
% => (minus (minus x))<не ynpo耻日(
(simp *(plus (sin 0)))
% => 0
(simp J(plus (exp 0)))
% => 1
(simp *(plus (log 1)))
% => 0
(simp 〉(minus (cosh 0)))
% => (минус 1)
(simp ;(minus (sin 0)))
% => 0
(simp ;(minus (arctan 0)))% => 0
(simp ;(plus x 0))
% => X
(simp 1(plus 0 x))
% => X
(simp J(minus x 0))
% => X
(simp J(times x 0))
% => 0
(simp J(quotient x 0))
% => бесконечность
(simp *(quotient Ox))
% => 0
(simp ,(minus (quotient x
0)))% => (минус бесконечность)
(simp J(power x 0))
% => 1
(simp J(power 0 x))
% => 0
(simp J(plus x y))
% => (plus х у)
(simp ,(plus (times x 0)
(times х 1)))% => х
(simp ,(minus (plus 0 x)
(times lx)))% => 0
% Приложения упрощений
(simp ,(minus (plus х у)
(minus у х)))
% => (minus (plus% => (minus (plusх у) (minus ух)) < не упрощено !
% => (minus (plus
% => (minus (plus
Дифференцирование
В этом разделе мы рассматриваем дифференцирование алгебраических (полиномиальных) выражений. Реализованы следующие правила:
=0, с — постоянная
=1
纟/(叫+ 9(叫)=¥ +学
糸/(?)-g(±))= m-m 总/⑺* 9(?))=/⑺* ^ +祭* 9(吟
В программе differ.si дифференцируется выражение ex, a v обозначает переменную. Выражение дается в префиксной нотации. В префиксной нотаи.йИ
ctcd ставится перед его операндами. Например, математическое выра-
*енке
(3 + /) * (е - а)
в®в®)ефиксной нотации с использованием Лиспа выглядит так:
в®
в®
(times (plus 3 х) (minus х а))
пелом программа действует, проверяя операторы и находя соответствие меж- ниМй. Когда соответствие найдено, к выражению применяется соответству- 當ее правило дифференцирования. Иногда после дифференцирования выра- *енпе становится более сложным. Возникает необходимость упрощения выражения. Поэтому когда возникает такой случай, мы используем программу упрощения simp3. si, которую мы рассмотрели в конце предыдущего раздела.
с некоторыми приложениями следуюс некоторыми приложениями следуюВыполнение функции diff, вместе
с некоторыми приложениями следую
с некоторыми приложениями следую
щее:
X differ.si
(de diff (ex v)(cond((atom ex)(cond ((eq ex v) 1) (t 0)))(de diff (ex v)(cond((atom ex)(cond ((eq ex v) 1) (t 0)))% d(ex)/dv% d(v)/dv = 1, d(constant)/dv = 0% d(ex)/dv% d(v)/dv = 1, d(constant)/dv = 0
(de diff (ex v)
(cond
((atom ex)
(cond ((eq ex v) 1) (t 0)))
(de diff (ex v)
(cond
((atom ex)
(cond ((eq ex v) 1) (t 0)))
% d(ex)/dv
% d(v)/dv = 1, d(constant)/dv = 0
% d(ex)/dv
% d(v)/dv = 1, d(constant)/dv = 0
((eq (car ex) 〉plus)((eq (car ex) 〉plus)% d(a+b)/dv = da/dv + db/dv% d(a+b)/dv = da/dv + db/dv(list 'plus(list 'plus(diff (cadr(diff (cadrv) (diff (caddr ex)v) (diff (caddr ex)v)))v)))
((eq (car ex) 〉plus)
((eq (car ex) 〉plus)
% d(a+b)/dv = da/dv + db/dv
% d(a+b)/dv = da/dv + db/dv
(list 'plus
(list 'plus
(diff (cadr
(diff (cadr
v) (diff (caddr ex)
v) (diff (caddr ex)
v)))
v)))
((eq (car ex) Jtimes)% d(a*b)/dv = da/dv * b + a * db/dv
(list 'plus
(list Jtimes (diff (cadr ex) v) (caddr ex))
(list Jtimes (cadr ex) (diff (caddr ex) v))))
))((eq (car ex) Jminus)(list 'minus (diff (cadr((eq (car ex) Jminus)(list 'minus (diff (cadr% d(a-b)/dv = da/dv - db/dv ex) v) (diff (caddr ex) v))))% d(a-b)/dv = da/dv - db/dv ex) v) (diff (caddr ex) v))))
)
)
((eq (car ex) Jminus)
(list 'minus (diff (cadr
((eq (car ex) Jminus)
(list 'minus (diff (cadr
% d(a-b)/dv = da/dv - db/dv ex) v) (diff (caddr ex) v))))
% d(a-b)/dv = da/dv - db/dv ex) v) (diff (caddr ex) v))))
Z Приложения
(diff (diff (diff (diff
Jx 7)
>2 7) 2 >x)
5x U
% => 1
% => 0
% => 0
% => 0
(diff
)(plus
X x) 〉x)
=>
(plus
1 1)
(diff
〉(times
X x) 7)
=>
(plus
(times 1 x) (times x 1)
(simp
(diff ,
(times x x) 〉x))
=>
(plus
X x)
(diff 1(times (plus 3 х) (minus а х)) ?х)
Z => (plus (times (plus 0 1) (minus а х))
% (times (plus 3 х) (minus О 1)))
(simp (diff ?(times (plus 3 x) (minus a x)) Jx))
% => (plus (minus a x) (times (plus 3 x) (minus 1)))
(diff J(times (times x x) x) Jx)
% => (plus (times (plus (times 1 x) (times 1 x)) x)
% (times (times x x) 1))
(simp (diff ?(times (times x x) x) 7))
% => (plus (times (plus x x) x) (times x x))
Система Лисп, основанная на C++
В этом разделе мы показываем, как Лисп может быть реализован, используя объектно-ориентированное программирование на C++. В программе мы реализуем следующие функции:
car cdr atom cons cond append
Базисными структурами данных системы Лисп являются атом и точечная пара. Списки формируются из точечных пар. Список может состоять из основных типов данных (например, int, double) или абстрактных типов данных (например, String, Verylong, Matrix).
(атом)(атом)В основном программа состоит из трех классов, связанных друг с другом, как показано на рис. 9.1.
(атом)
(атом)
(точечная пара)
Рис. 9.1. Принципиальная схема зависимости наследования.
Иерархия наследования абстрактного класса Element состоит из двух пр°" изводных классов: Pair и Туре. Класс Pair соответствует точечным пара" Лиспа, а класс Туре соответствует атомам в Лиспе.
Класс Element, который является абстрактным классом, определяет т°ль" ко интерфейс, но не выполнение методов, доступных в классах-потомках. В‘‘ функции в классе объявлены как virtual. Это позволяет классам-потомкам
вещать определения функций. Имеются четыре метода, объявленных ^ассе:
virtual Element* clone() const = 0;
0H объявляет функцию, которая дублирует объект из Element.
virtual void print(ostreamfe) const = 0;
0H объявляет функцию, которая отображает содержимое объекта из Element.
(virtual int atom() const = 0;
он объявляет функцию, которая проверяет, является ли объект из Element атомом.
(friend ostreamfe operator < < (ostreamfe,const Element *);
0H объявляет функцию потокового вывода.
Структуру и функциональные возможности класса Pair можно сформули- звать следующим образом:
Поля данных: класс содержит только два поля данных _саг и _cdr. Они представляют собой первый и второй элементы точечной пары соответственно.
Конструкторы и деструктор:
——Pair(): заданный по умолчанию конструктор назначает NULL полям _саг и _cdr.
—Pair (const Element *ee): он назначает ее полю _саг и NULL полю _cdr.
—Pair(const Element *el, const Element *e2): он назначает el и e2 первому и второму элементам точечной пары, соответственно.
—В классе также перезагружены конструктор копирования и деструктор.
Методы:
— Element* car() const;
возвращает первый элемент точечной пары.
— Element* cdr() const;
возвращает второй элемент точечной пары.
—void car(const Element *ee);
устанавливает первый элемент точечной пары к значению ее.
—void cdr(const Element *ee);
устанавливает второй элемент точечной пары к значению ее.
一 Виртуальные функции
Element* clone() const;
void print(ostreamfe) const;
int atom() const;
а оператор потокового вывода
ostreamfe operator << (ostreamfe,const Pair*);
перегружены с новыми определениями для класса.
Класс Туре реализован как шаблонный класс. Его структура и фун^ нальные возможности могут быть сформулированы следующим образом-"以
Поле данных: единственный компонент данных в классе есть thing 口 представляет численное значение образца класса Туре.Н。
Конструкторы:
пз1aпз1a——Type(): заданный по умолчанию конструктор обнуляет значение
пз1a
пз1a
thing.
thing.thing.—Type (Т vv): он устанавливает vv в качестве значения поля
thing.
thing.
—Конструктор копирования перезагружен.
Метода:
— const Т& value() const; возвращает значение поля thing.
—Виртуальные функции
Element clone() const; void print(ostreamfe) const; int atom() const;
и оператор выходного потока
ostreamfe operator < < (ostreamfe,const Type < T> *); перегружены с новыми определениями для класса.
Класс был произведен из класса Element, чтобы обеспечить наличие атомов nil и t. Атомы используются как функциональные параметры при помощи оператора адресации (&), например cons(&t, &nil); отметим, что только функция at от () объявлена как метод класса. Остальная часть функций, которые мы намереваемся реализовать, объявлена как глобальные функции:
[口七 is_nonempty_lisp.list (Element *1);
'Определяет, указывает ли 1 на подходящий непустой список Лиспа.
Element *cond (Element *с);
• Осуществляет условную инструкцию Лиспа cond; с должен указывать на непусто货 список списков, где каждый список представляет условие для исполнения; cond возвращает &nil, если ни одно условие не выполняется.
Ниже мы приводим программу, в которой реализована наша конструкция. В листинг добавлена прикладная программа lisprun.срр, в которой использу- 10Tсх функции из новых классов. Отметим, что точечные пары могут содер- 源1Tb элементы различных типов, таких, как 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
Sass —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 &s) const {s<<((is_t)?"t":"nil");}
Element *clone(void) const {return (Element*)this;} int atom(void) const {return 1;}
} nil(0),t(i);
?ass Pair : public Element // Точечная пара
Private:
Element *_car;// Первый элемент точечной пары
Element *_cdr;// Второй элемент точечной пары
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 atom() const;
// функция содружества
friend ostream & operator << (ostream &, const Pair *);
};
template <class T> class Type : public Element // Atom
private:
T thing;
public:
// Конструкторы
TypeO ;
Type(T);
Type(const Type<T> &);
// Метода
const T & value() const;
Type<T> & operator = (const Type<T> &);
Element * clone() const;
void print(ostream &) const;
int atom() const;
// Функция содружества
friend ostream & operator << (ostream &, const Type<T> *)
};
.глобальные функции
/ ・工 * cons(const Element *, const Element *);
einent * append (const Element * , const Element *);
‘;:晔口七 * car (const Element *);
}emen七 * cdr(const Element *);
// реализация
〃 Класс Pair
paM::Pair(): _car(&nil) , _cdr(&nil) {}
pair::Pair(const Element *e) : _car(e->clone()), _cdr(&nil) {}
pair::Pair(const Element *ei, const Element *e2): ^car(el->clone()), _cdr(e2->clone()) {}
pair::Pair(const Pair &p)
{
_car 二 &nil;
_cdr = &nil;
♦this = p;
}
Pair: : "Pair()
//нельзя удалять системные константы
if((_car != &nil)&&(_car != &t))
delete _car; _car = &nil;
if((_cdr != &nil)&&(_cdr != &t))
delete _cdr; _cdr = &nil;
Pair& Pair::operator = (const Pair &p)
if(this != &p)
if((_car != &nil)&&(_car != &t)) delete _car;
if((_car != &nil)&&(_car != &t)) delete _cdr;
_car = (p.car())->clone();
_cdr = (p.cdr())->clone();
return *this;
Element * Pair::car() const { return 一car; }
Element * Pair::cdr() const { return _cdr; }
void Pair::car(const Element *e)
if(e != &nil) _car = e->clone();
void Pair::cdr(const Element *e)
if(e != &nil) _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 &os, const Pair *p)
if(p != NULL)
p->print(os); return os;
os << "nil"; return os;
ostreamfe operator << (ostream &os, const Element *e)
if (e != NULL)
e->print(os);
return os;return os;
return os;
return os;
« Fil";
return os;
template template template { thingtemplate template template { thing<class T> Type<T>::Type() : thing(T(0)) {}
template template template { thing
template template template { thing
<class T> Type<T>::Type(T t) : thing(t) {}
<class T> Type<T>::Type(const Type<T> &t) =t.value(); }
(const Type<T> &t)(const Type<T> &t)template <class T>
(const Type<T> &t)
(const Type<T> &t)
Type<T> & Type<T>: : operator =
if(this != &t) thing = t.value();
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 { os << thing; }
template <class T>
^stream& operator << (ostreamfe os, const Type<T> *t)
^'>рг1пг(os);
return, os;
Pa ■
孤【* cons(const Element *el, const Element *e2)
Pair *p = new Pair; p->car(el);
p->cdr(е2);
return р;
Element* append(const Element *el, const Element *e2)
if(! el->atom())
Pair *p = new Pair(*(Pair *)el);
Pair *e = p;
while(e->cdr() != &nil) 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)
〔 c->atom()) return 0;
":М口 js-lisp-list(((Pair*)l)->cdr());
}
]№口七 *cgd(Element *e)
^.f(iS-.nonempty_lisp_list(e))
^Element *firstcase=((Pair*)e)->car();
if (is„noneinpty.lisp_list (first case))
^Element *condition=((Pair*)firstcase)->car();
if(condition->atom())
{
if (condition==&nil)
return cond(((Pair*)e)->cdr());
else if(condition==&t)
return ((Pair*)firstcase)->cdr();
else cerr<<Hcond ожидает, что первый элемент - nil или t. "«endl; }
else cerr<<"cond ожидает, что первый элемент - nil или t."<<endl; }
else cerr<<"cond ожидает списка для каждого случая."<<endl;
}
else cerr<<"cond ожидает список для выполнения."«endl;
return &nil;
}
#endif
Следующая программа использует только что описанные классы из Lisp.h. 〃 lisprun. срр
^include <iostream.h>
#include "Lisp.h"
typedef typedef typedef Wpedeftypedef typedef typedef Wpedef#include "Verylong.h"
typedef typedef typedef Wpedef
typedef typedef typedef Wpedef
Type<int> l_int;
Type<char> l_char;
Type<double> l_double;
Type<Verylong> l_verylong;
;“Sent *plu.s (Element *e)
迁(is_nonempty_lisp_list(e))
if (is_nonempty_lisp-list(cdr(е)))
// выберем 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 &nil;
Element* length_list(Element *e)
l_int zero(0),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 main()
// Определим два атома
1-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 J1 J а)
cout <<
11 (cons ; 1 *a)=>
"« с «
endl;
C = cons(&a,B);//
в Лиспе:
(cons * 1 ;(a))
cout <<
"(cons J1 ,(a))=>
” « С «
endl;
C = cons(A,&b);//
в Лиспе:
(cons ?(1) Ja)
cout <<
n(cons,(1) Ja) =>
'* « С «
endl;
cons(A,B);〃« "(cons У) Ya))=> « endl;cons(A,B);〃« "(cons У) Ya))=> « endl;в Лиспе: (cons ,(i) *(а)) "<< С << endl;в Лиспе: (cons ,(i) *(а)) "<< С << endl;
cons(A,B);〃
« "(cons У) Ya))=> « endl;
cons(A,B);〃
« "(cons У) Ya))=> « endl;
в Лиспе: (cons ,(i) *(а)) "<< С << endl;
в Лиспе: (cons ,(i) *(а)) "<< С << endl;
« '*Примеры для функции append: " « endl;
〃(setq 01 41 2 3))
pair *D1 =
cOns(new l„int(l),cons(new l_int(2),cons(new l_int(3),&nil)));
〃 (setq D2 ,(a b c))
pair *D2 = cons (new l.charC^1) , cons (new l_char(,b,), cons (new l_char('c'),&nil)));
pair *D == (Pair*) append (DI, D2);
coUt « " (append '(12 3),(a b c)) => " << D « endl;
cout « endl;
COut « "Примеры для функции car и cdr:" << endl;
// setq E ?((1 2 3) (a b c))
Pair
*E
= cons(DI,cons(D2,&nil));
cout
«
"(car '((1 2 3) (a b c)))=> ” << car(E« endl;
cout
«
"(car (car ?((1 2 3) (a b c))))=> " << car(car(E)) << endl;
cout
«
n(cdr J((1 2 3) (a b c)))=> " << cdr(E)<< endl;
cout
«
"(car (cdr J((1 2 3) (a b c))))=> " << car(cdr(E)) << endl;
cout
«
"(cdr (cdr ?((1 2 3) (a b c))))=> ” << cdr(cdr(E)) « endl;
cout
«
"(car J a) => ” << car(&a) « endl;
cout
«
endl;
〃 Абстрактный тип данных
cout << "Приложение с абстрактным типом данных: Verylong'* « endl;
1-verylong v("123456789012");
i-double r(3.14159);
Pair *Very = cons(&v, cons(&r,&nil));
Sut << '»(cons J v } (r)) => ” « Very << endl;
cout « endl << "Приложение с функцией cond:" << endl;
〃((ni]_ r) (t v))
8ut « "(cond (nil 3.14159) (t 123456789012))=> "
« cond(cons(cons(&nil,cons(&r,&nil)), cons(cons(&t,cons(&v,&nil)),&nil)))
<< endl;
// ((t г) (nil V))
cout « "(cond (t 3.14159) (nil 123456789012))=> "
<< cond(cons(cons(&t,cons(&r,&nil)), cons(cons(&nil,cons(&v,&nil)),&nil)))
<< endl;
Pair *test=cons(&a,cons(&b,cons(&v,cons(&r,&nil))));
cout << endl << "(a b v r) => ” << test << endl;
cout << " (length.list (abv r))=>"« length^list (test) « endl-
Результат
Примеры для фушщии (cons J1 Ja) => (cons s 1 Ma)) => (cons J(1) Ja) => (cons J(1) ,(a))=>
cons:
(1 • a)
(1 . (a . nil))
((1 . nil) . a)
((1 . nil) . (a . nil))
Примеры для фушщии (append 〉(1 2 3)'(a
append:
b c)) => (1 . (2 . (3 , (a . (b . (c . nil))))))
Примеры для фушщии (car J((l 2 3) (a b (car (car '((1 2 3) (cdr 〉((1 2 3) (a b (car (cdr ,((1 2 3) (cdr (cdr >((1 2 3)
car и cdr:
c)))=> (1 • (2 . (3 • nil)))
(a b c)))) => 1
c)))=> ((a . (b . (c . nil))) . nil)
(a b c)))) => (a . (b . (c . nil))) (a b c)))) => nil
car: нельзя применять car к атому!
(car 1 a) => nil
Приложение с абстрактным типом данных: Verylong
(cons Jv О) => (123456789012 . (3.14159 . nil))
Приложение с фуыщией cond:
(cond (nil 3.14159) (t 123456789012)) => (123456789012 • nil)
(cond (t 3.14159) (nil 123456789012)) => (3.14159 . nil)
(a b v r) => (1 . (a . (123456789012 • (3.14159 . nil))))
(length_list (abv r)) => (4 . nil)
眦 ва 1°
Дистинги программ
Глава содержит листинги заголовочных файлов классов, представленных в главах 6 и 7. Для каждого класса кратко описываются открытые методы и затем дается полный листинг.
Класс Verylong
Открытый интерфейс класса Verylong:
Verylong(const char* = NULL): конструктор.
Verylong (int): конструктор.
abs(const Verylongfe): функция абсолютной величины.
sqrt (const Verylongfe): функция целочисленного квадратного корня.
pow(const Verylong&, const Verylongfe): функция возведения в целую степень.
div(const Verylongfe, const Verylongfe): функция деления с двойной точностью.
арифметические операторы: ++, ——, -(унарный), +, -, *, /, %, =, +=, у, *=, /=, %=
операторы сравнения: ==, !=, <, <=, >, >=
операторы преобразования типа: operator int (), operator double ()
потоковые операторы: », <<
Для детального описания структуры класса и каждого из методов, пожа- "У近ста, обращайтесь к разд. 6.L
〃 Verylong.h
沪 def VERYLONG.H
define VERYLONG_H
:"elude <assert.h>
产Qude <iostream.h>
#:clude <iomcinip.h>
「elude《string.h>
#jSude <stdlib.h>
"elude <ctype.h>
#include ^limits.h>
#include <math.h>
class Verylong
{
private:
// Поля данных
// Строка, записанная в обратном порядке.// Длина строки Verylong// Знак числа Verylong: +=>0; -=>1// Строка, записанная в обратном порядке.// Длина строки Verylong// Знак числа Verylong: +=>0; -=>1char *vlstr;
// Строка, записанная в обратном порядке.
// Длина строки Verylong
// Знак числа Verylong: +=>0; -=>1
// Строка, записанная в обратном порядке.
// Длина строки Verylong
// Знак числа Verylong: +=>0; -=>1
int vlen;
int vlsign;
// Обращение строки s// Обращение строки s// Закрытые методы
// Обращение строки s
// Обращение строки s
char *strrev(char *s) const;
Verylong multdigit(int) const;
Verylong mult10(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 Verylong Verylong Verylong Verylong
operator operator operator operator operator
-()const; //
оператор оператор оператор оператор оператор
смены знака“
инкремента префиксный^ инкремента постфиксной декремента префиксный^ декремента постфиксный
++ 0;
++ (int);
—0;
―(int);
〃 〃 //
//
Verylong
operator
+= (const
Verylong &);
Verylong
operator
-=(const
Verylong &);
Verylong
operator
*= (const
Verylong &);
Verylong
operator
/= (const
Verylong &);
Verylong
operator
%= (const
Verylong &);
friend friend friend Friend friend
Verylong Verylong Verylong Verylong
Verylong
operator + operator - operator * operator / operator %
(const Verylong &, const Verylong &); (const Verylong &, const Verylong &);
(const Verylong &, (const Verylong &,
const Verylong &); const Verylong &);
(const Verylong
&,
const
Verylong &);
£riend
int operator == (const Verylong &,
const Verylong &);
friend
int operator != (const Verylong &,
const Verylong &);
friend
int operator < (const Verylong &,
const Verylong &);
friend
int operator <= (const Verylong &,
const Verylong &);
friend
int operator > (const Verylong &,
const Verylong &);
friend
int operator >= (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 &, const Verylong &); friend istream & operator >> (istream &, Verylong &);
};
// Данные класса
const Verylong Verylong::zero = Verylong(n0");
const Verylong Verylong::one = Verylong("1");
const Verylong Verylong::two = Verylong("2");
〃 Конструкторы, деструкторы и операторы преобразования.
Verylong::Verylong(const char *value)
if (value)
vlsign.二(*value == '-') ? 1:0;
if (ispunct(*value))
vlen = strlen(value)-l;
vlstr = new char[vlen + 1] ; a, strcpy(vlstr, value+1);
else
vlen. = strlen(value);
vlstr = new char [vlen + 1] ; a.
// проверка на знак минус
// если первый символ есть // знак препинания.
ssert(vlstr != NULL);
ssert(vlstr != NULL);
strcpy(vlstr, value);
strrev(vlstr);
else // инициализируем нулем
vlstr == new char[2] ; assert(vlstr != NULL);
*vlstr = O; * (vlstr+1) =,\0,; vlen == 1; vlsign = 0;
Verylong::Verylong(int n)
int i;
// проверка знака и смена его// проверка знака и смена его// для отрицательного числа// для отрицательного числаif (n<0) {vlsign = 1; n = (-n); } else vlsign = 0;
// проверка знака и смена его
// проверка знака и смена его
// для отрицательного числа
// для отрицательного числа
if (п)
i = (int)logl0(n)+2;// проверка длины числа
vlstr = new char[i]; assert(vlstr != NULL);
vlen == i-1;
i = 0;
while (n >= 1)// выделение цифр числа по одной и запись
{// их во внутреннее представление числа
vlstr [i] = n%i0 + ?07;
и /== 10; i++;
vlstr[i] = ,\0,;
else// иначе число есть нуль
vlstr == new char [2] ; assert(vlstr != NULL);
*vlstr = U; * (vlstr+1) =,\Ф;
vlen = 1;
Verylong::Verуlong(const Verylongfe x):vlen(x.vlen),vlsign(x.vlsign)
vlstr = new char [x.vlen + 1] ; assert (vlstr !== NULL); strcpy(vlstr, x.vlstr);
}
ylong: <Verylong() { delete 口 vlstr; }
-ylong::operator int() const
static Verylong maxO(INT_MAX);
static Verylong minO(INT_MIN+l);
int number, factor=l;
血*this > maxO)
{
cerr << "Ошибка: преобразование Verylong->integer невозможноM << 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;
sumsum=double(vlstr [0]-*0 0 ;=double(vlstr [0]-*0 0 ;Verylong::operator double() const double sum, factor = 1.0;
sum
sum
=double(vlstr [0]-*0 0 ;
=double(vlstr [0]-*0 0 ;
for (int i=l; Kvlen; i++)
factor *= 10.0;
sum += double(vlstr[i]-?0?) * factor;
if (vlsign) return -sum; return sum;
char *temp = new char [vlen + 1] ; assert (temp != NULL);
char *s;
if (vlen > 0)
{
strcpyCtemp, 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 JO");
delete [] temp;
return s;
// Различные методы
const Verylong & Verylong::operator = (const Verylong &rhs)
if (this == &rhs) return *this;
delete [J 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;
}
нкременна префиксный Wj£*°perg ++ () ng
'卩'…=*this + one;
.*th】s
“МП
'инкремента постфиксный
°Mpa 噹ry“ng:: °Perat°r ++ (^t)
;el 严"g
“ylggZSis);
…二 *this + one;
*th】s
return result;
,оператор декремента префиксный •e.ylong Verylong: operator — ° '-return *this = *this - one; }
/ оператор декремента постфиксный ■;erylong Verylong: operator — (int)
Verylong result(*this);
*this = *this - one;
return result;
(const Verylongfe v)(const Verylongfe v)■eryhng Verylong: :operator return *this = *this - v; }■eryhng Verylong: :operator return *this = *this - v; }(const(constVerylongfeVerylongfev)v)Verylong: :operator *= ем *this = *Qis * v; }У嚣n%;W 署raj口丄s - *this / v; }Verylong: :operator *= ем *this = *Qis * v; }У嚣n%;W 署raj口丄s - *this / v; }(const(const(const(constVerylongfeVerylongfeVerylongfeVerylongfev)v)v)v):代口心 еГу1опё: operator %=〃 *thls = *Ws % v; } 餐Р'М^кые tt:"。口g о» "РУ*еснвенные операторы и функции.& °r + (const Verylong &u, const Verylong &v)v.vlsign):代口心 еГу1опё: operator %=〃 *thls = *Ws % v; } 餐Р'М^кые tt:"。口g о» "РУ*еснвенные операторы и функции.& °r + (const Verylong &u, const Verylong &v)v.vlsign)(const(constVerylongfeVerylongfev)v)电ylctng Verylong: : operator += 'return *this = *this + v; }
(const Verylongfe v)
(const Verylongfe v)
■eryhng Verylong: :operator return *this = *this - v; }
■eryhng Verylong: :operator return *this = *this - v; }
(const
(const
Verylongfe
Verylongfe
v)
v)
Verylong: :operator *= ем *this = *Qis * v; }
У嚣n%;W 署raj
口丄s - *this / v; }
Verylong: :operator *= ем *this = *Qis * v; }
У嚣n%;W 署raj
口丄s - *this / v; }
(const
(const
(const
(const
Verylongfe
Verylongfe
Verylongfe
Verylongfe
v)
v)
v)
v)
:代口心 еГу1опё: operator %=
〃 *thls = *Ws % v; } 餐Р'М^кые tt
:"。口g о» "РУ*еснвенные операторы и функции.
& °r + (const Verylong &u, const Verylong &v)
v.vlsign)
:代口心 еГу1опё: operator %=
〃 *thls = *Ws % v; } 餐Р'М^кые tt
:"。口g о» "РУ*еснвенные операторы и функции.
& °r + (const Verylong &u, const Verylong &v)
v.vlsign)
(const
(const
Verylongfe
Verylongfe
v)
v)
if (u.vlsign == 0) { else{if (u.vlsign == 0) { else{}}Verylong tl = u-abs(v);Verylong t2 = v-abs(u);Verylong tl = u-abs(v);Verylong t2 = v-abs(u);return tl;}return t2;}return tl;}return t2;}
if (u.vlsign == 0) { else{
if (u.vlsign == 0) { else{
}
}
Verylong tl = u-abs(v);
Verylong t2 = v-abs(u);
Verylong tl = u-abs(v);
Verylong t2 = v-abs(u);
return tl;}
return t2;}
return tl;}
return t2;}
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)// если есть перенос,
digitsum -= 10;carry = 1;elsecarry = 0;temp[j] = digitsum + ,0,;if (carry)temp[j++] = ,1,;if (u.vlsign) temp[j++]= 』temp[jJ = ,\0>;u.strrev(temp);Verylong result(temp);delete [J temp;return result;digitsum -= 10;carry = 1;elsecarry = 0;temp[j] = digitsum + ,0,;if (carry)temp[j++] = ,1,;if (u.vlsign) temp[j++]= 』temp[jJ = ,\0>;u.strrev(temp);Verylong result(temp);delete [J temp;return result;/ / уменьшаем сумму на 10/ / уменьшаем сумму на 10// установим carry в 1// в противном случае carry есть О// вставка символа в строку// если carry закончилось,// последняя цифра есть 1// установим carry в 1// в противном случае carry есть О// вставка символа в строку// если carry закончилось,// последняя цифра есть 1// заканчиваем строку// заканчиваем строку
digitsum -= 10;
carry = 1;
else
carry = 0;
temp[j] = digitsum + ,0,;
if (carry)
temp[j++] = ,1,;
if (u.vlsign) temp[j++]= 』
temp[jJ = ,\0>;
u.strrev(temp);
Verylong result(temp);
delete [J temp;
return result;
digitsum -= 10;
carry = 1;
else
carry = 0;
temp[j] = digitsum + ,0,;
if (carry)
temp[j++] = ,1,;
if (u.vlsign) temp[j++]= 』
temp[jJ = ,\0>;
u.strrev(temp);
Verylong result(temp);
delete [J temp;
return result;
/ / уменьшаем сумму на 10
/ / уменьшаем сумму на 10
// установим carry в 1
// в противном случае carry есть О
// вставка символа в строку
// если carry закончилось,
// последняя цифра есть 1
// установим carry в 1
// в противном случае carry есть О
// вставка символа в строку
// если carry закончилось,
// последняя цифра есть 1
// заканчиваем строку
// заканчиваем строку
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;>
int maxlen = (u.vlen>v.vlen) ? u.vlen:v.vlen.
d, dl, d2, i, negative, borrow=0;
Verylmg w,y;
辻(u.vlsign辻(u.vlsign=0)=0)// оба u,v положительны// оба u,v положительныif (u<v)if (u<v){{else elseelse elsew=v;w=u;w=v;w=u;y=u; negative=l;} y=v; negative=0;}y=u; negative=l;} y=v; negative=0;}// оба u,v отрицательны// оба u,v отрицательныif (u<v) elseif (u<v) else{ w=u;{ w=v;{ w=u;{ w=v;y=v; negative=l;} y=u; negative=0;}y=v; negative=l;} y=u; negative=0;}
辻(u.vlsign
辻(u.vlsign
=0)
=0)
// оба u,v положительны
// оба u,v положительны
if (u<v)
if (u<v)
{
{
else else
else else
w=v;
w=u;
w=v;
w=u;
y=u; negative=l;} y=v; negative=0;}
y=u; negative=l;} y=v; negative=0;}
// оба u,v отрицательны
// оба u,v отрицательны
if (u<v) else
if (u<v) else
{ w=u;
{ w=v;
{ w=u;
{ w=v;
y=v; negative=l;} y=u; negative=0;}
y=v; negative=l;} y=u; negative=0;}
for (i=0; Kmaxlen; i++)
dl = (i>w.vlen-1) ? 0:w.vlstr[i]-J 0J;
d2 = (i>y.vlen-1) ? 0:y.vlstr[i]-'0‘;
d = dl - d2 - borrow;
if(d < 0)// если d меньше 0
////d += 10; borrow =d += 10; borrow =1 занимается из следующей хщфры1 занимается из следующей хщфры{// 10 прибавляется к d и
//
//
d += 10; borrow =
d += 10; borrow =
1 занимается из следующей хщфры
1 занимается из следующей хщфры
} else
borrow = 0;// иначе нет необходимости в занимании
temp[i] = d +〉0 匕
while (i-1 > 0 && temp [i-1] == ? 03)// если результат нулевой
if (negative) temp [i++] =、,; temp[i]二 J\05;
u.strrev(temp);
Verylong result(temp);
delete [] temp; return result;
long operator * (const Verylong 也u, const Verylong &v)
Verylgg pprodC'l") ? tempsumC'O");
for (int j=0; j<v.vlen; j++)
int digit = v.vlstr[j] - J0J; // выделяем хщфру
pprod = u.multdigit(digit); pprod = pprod.multiO(j);
temp sum = tempsum + pprod;
//
//
// // //
умножение на хщфру
"добавляем" нули сзади
соответствуй^
результат прибавляется к tempsum
tempsum.vlsign = u.vlsign"v.vlsign; // определяем знак return tempsum;
// Это алгоритм длинного деления
Verylong operator / (const Verylong &u, const Verylong &v)
Verylong w,у,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.mult10(1);
b = d = Verylong: :zero; // инициализация b и d значением 0 while (b < c)
{ b = b + y; d = d + Verylong::one;}
if (c < b)// если b>c, то значит
{// мы добгсвили счетчику слишком много
Ь = b - у;
d = d - Verylong::one;
quotient = quotient + d; // прибавляем к частному if (i < len)
c = (c-b).multlO(l);
二 с + Verylong(w.vistr[len-i-1] -J 05 );
Return quotient;
operator % (const Verylong &u, const Verylong &v)
return (u - ”*(u/v));
jut 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 else ifif else if(u.ulsign <(u.ulsign <v.vlsign) returnv.vlsign) return(u.vlsign > v.vlsign) return(u.vlsign > v.vlsign) return(u。¥〕en <(u。¥〕en <0;1;0;1;v.vlen) return (1ли.vlsign); // XOR L)v.vlen) return (1ли.vlsign); // XOR L)else if (u.vlen > v.vlen) return (0"u.vlsign); // для определенияelse if (u.vlen > v.vlen) return (0"u.vlsign); // для определения
if else if
if else if
(u.ulsign <
(u.ulsign <
v.vlsign) return
v.vlsign) return
(u.vlsign > v.vlsign) return
(u.vlsign > v.vlsign) return
(u。¥〕en <
(u。¥〕en <
0;
1;
0;
1;
v.vlen) return (1ли.vlsign); // XOR L)
v.vlen) return (1ли.vlsign); // XOR L)
else if (u.vlen > v.vlen) return (0"u.vlsign); // для определения
else if (u.vlen > v.vlen) return (0"u.vlsign); // для определения
// знака
int temp;
char *templ = new char[u.vlen+1]? *temp2 二 new char[v.vlen+1];
assert(tempi != NULL);
assert(temp2 !二 NULL);
strcpy (temp 1 ;, u. vlstr);
“trcpy(temp2, v.vlstr);
u-strrev(templ);
u-strrev(temp2);// u - вспомогательная переменная
'етр = strcmp(tempi? temp2);
delete [J tempi; delete □ temp2;
if (temp < 0)
Return (l"u.vlsign); // используем XOR(宀) для определения знака
卧
else if (temp > 0)
return (0"u.vlsign);
else
return 0;// если u==v, возвращаем 0
}
int operator <= (const Verylong &u, const Verylong &v)
return (u<v I I u==v);
}
int operator > (const Verylong &us 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;
〃 разбиваем число на группы по 2 хщфры
好(v.vlen % 2) digitsum = Verylong(w[k--] - '(Г); eise
〈digitsum = Verylong((w[k] - ?0*)*10 + w[k-l] -,0,); к "= 2;
〃 находим первую цифру целочисленного квадратного корня sum = z = Verylong(int(sqrt(double(digitsum))));
〃 запоминаем частичный результат
temp [0] = int(z) +,0\
digitsum = digitsum - z*z;
for(; j<num; j++)
{
// берем следующую цифру числа
digitsum = digitsum.mult 10(1) + Verylong(w[k—] - О);
у = z + z;// 2*a
z = digitsum/y;
tempsum = digitsum.mult10(1) + Verylong(w[k] - J0J); digitsum = -y*z.multlO(l) + tempsum - z*z;
// уменьшаем z на 1 и перевычисляем digitsum
// когда она переоценена.
while (digitsum < Verylong::zero)
digitsum = -y*z.multlO(l) + tempsum - z*z;
temp[j] = int(z) + J 0?;
z = sum = sum.mult10(1) + z;
temp[num] = ?\0J;
////Verylong result(temp); delete 口 temp; delete □ w; return result;
//
//
воэведение числа в степень degree
erylong pow(const Verylong &X, const Verylong fedegree)
Verylong N(degree), Y("l"), х(Х);
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)
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 - у.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;
Verylong::zero;
d
0; // инициализируем b и d значением 0
while (h < c)
// если b>c, то значит// мы добавили слишком много// если b>c, то значит// мы добавили слишком много{ b 屮二 У; C += 1;}
// если b>c, то значит
// мы добавили слишком много
// если b>c, то значит
// мы добавили слишком много
if (c < b)
qq +二 dQLiDi_e(d), c 二(с-Ъ).multlO(l);qq +二 dQLiDi_e(d), c 二(с-Ъ).multlO(l);{ b -二 У; & -= i;}
qq +二 dQLiDi_e(d), c 二(с-Ъ).multlO(l);
qq +二 dQLiDi_e(d), c 二(с-Ъ).multlO(l);
// добавим к частному
// частичный остаток * 10
ifif(i < leu)//и прибавим к следующей цифре
if
if
с т= VerylongCw.vlstr[len~i-l]-J0?);
qq *= qqscale; count = 0;
while (c ,二 Verylong::zero && count < decno) {
qqscale 0.1;
Ъ = Verylong::zero; d - 0; // инициализация Ъ и d значением 0
while (b < с) { Ъ у: d +- 1;}
if (с<Ъ)если b>c, это значит
{ b у; d 1;}// мы добавили слишком много
qq +- double(d)*qqscale;
с - (c-b)=multl0(l); count++;
}
严earn &严earn &if (u,vlslgn^v.vlsign) qq *- (-1.0); // проверка знака return qq;
严earn &
严earn &
operator << (ostream &s, const Verylong &v)
char *temp 二 new char[v.vlen + 1]; assert(temp !二 NULL); if (v.vlen > 0)
strcp5z(temp, v. vlstr);
if (v.vlsign) s <<;
s << V.strrev(temp);
else s << "0";
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+1]; assert(v.vlstr != NULL);
strcpy(v.vlstr, temp);
return s;
}
// закрытые методы: strrev(), multdigit(), mult10().
//
// переворачиваем строку 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);
for (jfor (jн 0; j<vlen; j++)н 0; j<vlen; j++)intintdl = vlstr[j] - 10?,dl = vlstr[j] - 10?,if (digitprod >= 10)carry 二 digitprod/10;digitprod -二 carry*10;else carry = 0;temp[j] = digitprod + ?05;if (digitprod >= 10)carry 二 digitprod/10;digitprod -二 carry*10;else carry = 0;temp[j] = digitprod + ?05;if (carry)temp [j++] = carry +temp[j] = '\0‘;strrev(temp);Verylong result(temp);delete □ temp;return result;else return zero;if (carry)temp [j++] = carry +temp[j] = '\0‘;strrev(temp);Verylong result(temp);delete □ temp;return result;else return zero;digitprod = dl*num + carry;// если имеется новая цифра,// carry - старшая цифра// результат - младшая цифра// иначе carry равна 0// вставим символ в строку// if carry закончилась, // это - последняя цифра // закрываем строкуdigitprod = dl*num + carry;// если имеется новая цифра,// carry - старшая цифра// результат - младшая цифра// иначе carry равна 0// вставим символ в строку// if carry закончилась, // это - последняя цифра // закрываем строку
for (j
for (j
н 0; j<vlen; j++)
н 0; j<vlen; j++)
int
int
dl = vlstr[j] - 10?,
dl = vlstr[j] - 10?,
if (digitprod >= 10)
carry 二 digitprod/10;
digitprod -二 carry*10;
else carry = 0;
temp[j] = digitprod + ?05;
if (digitprod >= 10)
carry 二 digitprod/10;
digitprod -二 carry*10;
else carry = 0;
temp[j] = digitprod + ?05;
if (carry)
temp [j++] = carry +
temp[j] = '\0‘;
strrev(temp);
Verylong result(temp);
delete □ temp;
return result;
else return zero;
if (carry)
temp [j++] = carry +
temp[j] = '\0‘;
strrev(temp);
Verylong result(temp);
delete □ temp;
return result;
else return zero;
digitprod = dl*num + carry;
// если имеется новая цифра,
// carry - старшая цифра
// результат - младшая цифра
// иначе carry равна 0
// вставим символ в строку
// if carry закончилась, // это - последняя цифра // закрываем строку
digitprod = dl*num + carry;
// если имеется новая цифра,
// carry - старшая цифра
// результат - младшая цифра
// иначе carry равна 0
// вставим символ в строку
// if carry закончилась, // это - последняя цифра // закрываем строку
}
// умножаем это Verylong число на 10*num
Verylong Verylong: :multlO(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] = J0J;
temp[dd] = 1\0 J;
Verylong result(temp);
delete □ temp; return result;
} else return zero;
#endif
Класс Rational
Открытый интерфейс класса Rational:
Rational(): конструктор по умолчанию.
Rational(Т): конструктор целых чисел.
Rational(T,T): конструктор рациональных чисел.
П1лп(): числитель рационального числа.
den (): знаменатель рационального числа.
frac(): дробная часть рационального числа.
normalize(): нормализация рационального числа.
арифметические операторы: -(унарный), +, -, *, /,
операторы сравнения: ==, !=, <, <=, >, >=
операторы преобразования типа: operator double ()
потоковые операторы: >>, <<
Для детального описания структуры класса и каждс луйста, обращайтесь к разд. 6.2.
// Rational.h
// Класс радиональных чисел
#ifndef RATIONAL』
#define RATIONAL_H #include <iostream.h> #include <stdlib.h> #include <ctype.h>
template <class T> class Rational
private:
// Поля данных : числитель и знаменатель т p,q;
// внутренний метод
Т gcd(T,T);
public:
// конструкторы и деструктор
Rational();
Rational(Т);
Rational(T, Т);
Rational(const Rational<T> &);
"Rational();
// оператор преобразования operator double () const;
// методы
// числитель г// числитель гT П1ЛП() const;
// числитель г
// числитель г
& operator +=& operator -=& operator *=& operator /=& operator +=& operator -=& operator *=& operator /=const Rational<T> const Rational<T> const Rational<T> const Rational<T>const Rational<T> const Rational<T> const Rational<T> const Rational<T>friend Rational<T> operator +friend Rational<T> operator -friend Rational<T> operator +friend Rational<T> operator -friend Rational<T> operator *friend Rational<T> operator *friend Rational<T>friend Rational<T>operatoroperator(const Rational<T>&, const Rational<T>&);(const Rational<T>&, const Rational<T>&);(const Rational<T>&, const Rational<T>&);(const Rational<T>&, const Rational<T>&);(const Rational<T>&, const Rational<T>&);(const Rational<T>&, const Rational<T>&);(const Rational<T>&, const Rational<T>&);(const Rational<T>&, const Rational<T>&);friend int friend int friend int friend int friend int friend intfriend int friend int friend int friend int friend int friend intoperator == operator != operator > operator < operator >= operator <=operator == operator != operator > operator < operator >= operator <=(const Rational<T>&, (const Rational<T>&, (const Rational<T>&, (const Rational<T>&, (const Rational<T>&, (const Rational<T>&,(const Rational<T>&, (const Rational<T>&, (const Rational<T>&, (const Rational<T>&, (const Rational<T>&, (const Rational<T>&,const Rational<T>&); const Rational<T>&); const Rational<T>&); const Rational<T>&); const Rational<T>&); const Rational<T>&);const Rational<T>&); const Rational<T>&); const Rational<T>&); const Rational<T>&); const Rational<T>&); const Rational<T>&);
& operator +=
& operator -=
& operator *=
& operator /=
& operator +=
& operator -=
& operator *=
& operator /=
const Rational<T> const Rational<T> const Rational<T> const Rational<T>
const Rational<T> const Rational<T> const Rational<T> const Rational<T>
friend Rational<T> operator +
friend Rational<T> operator -
friend Rational<T> operator +
friend Rational<T> operator -
friend Rational<T> operator *
friend Rational<T> operator *
friend Rational<T>
friend Rational<T>
operator
operator
(const Rational<T>&, const Rational<T>&);
(const Rational<T>&, const Rational<T>&);
(const Rational<T>&, const Rational<T>&);
(const Rational<T>&, const Rational<T>&);
(const Rational<T>&, const Rational<T>&);
(const Rational<T>&, const Rational<T>&);
(const Rational<T>&, const Rational<T>&);
(const Rational<T>&, const Rational<T>&);
friend int friend int friend int friend int friend int friend int
friend int friend int friend int friend int friend int friend int
operator == operator != operator > operator < operator >= operator <=
operator == operator != operator > operator < operator >= operator <=
(const Rational<T>&, (const Rational<T>&, (const Rational<T>&, (const Rational<T>&, (const Rational<T>&, (const Rational<T>&,
(const Rational<T>&, (const Rational<T>&, (const Rational<T>&, (const Rational<T>&, (const Rational<T>&, (const Rational<T>&,
const Rational<T>&); const Rational<T>&); const Rational<T>&); const Rational<T>&); const Rational<T>&); const Rational<T>&);
const Rational<T>&); const Rational<T>&); const Rational<T>&); const Rational<T>&); const Rational<T>&); const Rational<T>&);
Т jen() const;// знаиенатель г
Rational<T> frac() const; // дробная часть г
void normalize () ;// нормализадия ргсционального числа
// арифметиче ские операторы и операторы сравнения const Rational<T> & operator = (const Rational<T> &); Rational<T> operator - () const;
(const Rational<T> &); (const Rational<T> &); (const Rational<T> &); (const Rational<T> &);
// функции потокового ввода/вывода
friend ostream & operator << (ostream &, const Rational<T>&); friend istream & operator » (istream &, Rational<T>&);
〃 конструкторы template <class template <class〃 конструкторы template <class template <classи деструктор и оператор преобразования.и деструктор и оператор преобразования.Т> Rational<T>::Rational Т> Rational<T>::RationalТ> Rational<T>::Rational Т> Rational<T>::RationalО : p(T(0)), q(T(l)) {} (Т N) : p(N), q(T(l)) {}О : p(T(0)), q(T(l)) {} (Т N) : p(N), q(T(l)) {}
〃 конструкторы template <class template <class
〃 конструкторы template <class template <class
и деструктор и оператор преобразования.
и деструктор и оператор преобразования.
Т> Rational<T>::Rational Т> Rational<T>::Rational
Т> Rational<T>::Rational Т> Rational<T>::Rational
О : p(T(0)), q(T(l)) {} (Т N) : p(N), q(T(l)) {}
О : p(T(0)), q(T(l)) {} (Т N) : p(N), q(T(l)) {}
templatetemplateT>T>Rational<T>::RationalRational<T>::Rational(T N, T D)(T N, T D)p(N), q(D)p(N), q(D)
template
template
T>
T>
Rational<T>::Rational
Rational<T>::Rational
(T N, T D)
(T N, T D)
p(N), q(D)
p(N), q(D)
static T zero(0);
if (D==zero)
cerr << "Нулевой знаиенатель рационгшьного числа ” << endl; return;
if (q < zero) {p = -p; q = -q;}
normalize();
template <class Т> Rational<T>::Rational(const Rational<T> &r) :p(r.p), q(r.q) {}
template <class T> Rational<T>::"RationalO {}
template <class T> Rational<T>::operator double() const { return double(p)/double(q); }
#include "Verylong.h"
Rational<Verylong>::operator double() const { return div(p,q); }
// методы template <class template <class
T> T Rational<T>::num() const { return p; }
T> T Rational<T>::den() const { return q; }
template <class T> Rational<T> Rational<T>::frac()
const
}
static Rational<T> zero(0) , one⑴; Rational<T> temp(*this);
if (temp < zero)
while (temp < return temp -
else
while (zero < return temp +
zero) temp = temp + one; one;
temp) temp = temp - one; one;
template <class T> void Rational<T>::normalize()
static T zero(0); static T one(l);
T t;
if (p < zero) t = -p;
elset = p;
t = gcd(t, q);
if (t > one) { p /= t; q /= t;}
// различные операторы
template <class T>
(const Rational<T> &r)(const Rational<T> &r)Rate <dass T>t & Rational<T> & Rational<T>: freturn *this = *this + r; }template〈class T> const Rational<T> & Rational<T>: { return *this = *this - r; }template <class T> conSt Rational<T> & Rational<T>: { return *this = *this * r; }template <class T> const Rational<T> & Rational<T>: { return *this = *this / r; }Rate <dass T>t & Rational<T> & Rational<T>: freturn *this = *this + r; }template〈class T> const Rational<T> & Rational<T>: { return *this = *this - r; }template <class T> conSt Rational<T> & Rational<T>: { return *this = *this * r; }template <class T> const Rational<T> & Rational<T>: { return *this = *this / r; }operator +=(constRational<T>&r)operator -=(constRational<T>&r)operator *=(constRational<T>&r)operator /=(constRational<T>&r)operator +=(constRational<T>&r)operator -=(constRational<T>&r)operator *=(constRational<T>&r)operator /=(constRational<T>&r)
(const Rational<T> &r)
(const Rational<T> &r)
Rate <dass T>
t & Rational<T> & Rational<T>: freturn *this = *this + r; }
template〈class T> const Rational<T> & Rational<T>: { return *this = *this - r; }
template <class T> conSt Rational<T> & Rational<T>: { return *this = *this * r; }
template <class T> const Rational<T> & Rational<T>: { return *this = *this / r; }
Rate <dass T>
t & Rational<T> & Rational<T>: freturn *this = *this + r; }
template〈class T> const Rational<T> & Rational<T>: { return *this = *this - r; }
template <class T> conSt Rational<T> & Rational<T>: { return *this = *this * r; }
template <class T> const Rational<T> & Rational<T>: { return *this = *this / r; }
operator +=
(const
Rational<T>
&r)
operator -=
(const
Rational<T>
&r)
operator *=
(const
Rational<T>
&r)
operator /=
(const
Rational<T>
&r)
operator +=
(const
Rational<T>
&r)
operator -=
(const
Rational<T>
&r)
operator *=
(const
Rational<T>
&r)
operator /=
(const
Rational<T>
&r)
Rational<Т> & Rational<T>::operator с"** q = r. q; return *this; } (广r・P,ч r
te <class T> Rational<T> Rational<T>::operator - () const 便;;七:工口 Rational<T>(-p,q); }
// различные дружественные операторы и функции.
template <class Т>
template <class T> Rational<T> operator { return Rational<T>template <class T> Rational<T> operator { return Rational<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 { return Rational<T>
template <class T> Rational<T> operator { return Rational<T>
-(const Rational<T> &rl,const Rational<T> &r2) (rl.p * r2.q - r2.p * rl.q, rl.q * r2.q); }
template <class T>
template <class T> Rationa工<t> operator { return Rational<T>template <class T> Rationa工<t> operator { return Rational<T>Hational<T> operator * (const Rational<T> &rlэ const Rational<T> &r2) { return Rational<T> (rl.p * r2.p, rl.q * r2.q); }
template <class T> Rationa工<t> operator { return Rational<T>
template <class T> Rationa工<t> operator { return Rational<T>
template <class T>;“t operator == (const t ratum (rl.p * r2.q)template <class T>;“t operator == (const t ratum (rl.p * r2.q)/ (const Rational<T> &rl5const Rational<T> &r2) (rl.p * r2.q, rl.q * r2.p); }
template <class T>
;“t operator == (const t ratum (rl.p * r2.q)
template <class T>
;“t operator == (const t ratum (rl.p * r2.q)
Rational<T> &rl, const Rational<T> &r2) ==(r2.p * rl.q); }
template <class T>
;nt operator != (const Ration.al<T> &rl, const Rational<T> &r2) Зит ! (rl == r2) ; }
template <class Т> int operator > (const
static T zero(0);
r2.p*rl.q > zero);r2.p*rl.q > zero);return (rl.p*r2.q -
r2.p*rl.q > zero);
r2.p*rl.q > zero);
template <class T>
int operator < (const Rational<T> &rl, const
static T zero(0);return (rl.p*r2.q -static T zero(0);return (rl.p*r2.q -r2.p*rl.q < zero);r2.p*rl.q < zero);
static T zero(0);
return (rl.p*r2.q -
static T zero(0);
return (rl.p*r2.q -
r2.p*rl.q < zero);
r2.p*rl.q < zero);
template <class T>
int operator >= (const Rational<T> &rl, const Rational<T> 也⑵ { 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 5 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)
////////s; // //s; // //
//
//
//
//
s; // //
s; // //
char c;
T n, d(l);
s.clear();
s >> n;
if (! s.good()) return c 二 s.peek();
if (c == /)
установка статуса потока читаем числитель
если не получено целое, в( получить следующий симв°л
// 〃// 〃очистка '/'читаем знаменательочистка '/'читаем знаменатель
// 〃
// 〃
очистка '/'
читаем знаменатель
очистка '/'
читаем знаменатель
с = s.get();
s >> d;
if (! s.goodO)
s. clear (s. rdstateO I ios : :badbit);
г.cnal<T>S,d);
} , RaG°na丄
:eM s:
T m =}.return a;T m =}.return a;а % Ь;а % Ь;a = b;a = b;b = m;b = m;
T m =
}.
return a;
T m =
}.
return a;
а % Ь;
а % Ь;
a = b;
a = b;
b = m;
b = m;
zero(0); > zero)zero(0); > zero)"噥噥黑言禮'"T>"Cd (T a, Tb) teXe
zero(0); > zero)
zero(0); > zero)
〃 Эта функция 〃 она должна(〃 Эта функция 〃 она должна(Т> Т abs(const Т &х)Т> Т abs(const Т &х)template <classtemplate <class:является глобальной, быть в глобальном заголовочном файле.
〃 Эта функция 〃 она должна(
〃 Эта функция 〃 она должна(
Т> Т abs(const Т &х)
Т> Т abs(const Т &х)
template <class
template <class
if (х>Т(0)) return х; return -х;
}
#endif
Класс Complex
Открытый интерфейс класса Complex.
Complex。: конструктор по умолчанию.
Complex (Т) : конструктор вещественного
* Complex(Т,Т): конструктор комплексного^^ 口望咨
raalPartO: вещественная часть ко
imagPartO: мнимая часть комплексного у"
Magnitude (): модуль комплексного числа.
* 貶gument () : угловой аргумент комплексног
* coujug^e(): сопряженное комплексное число.
* negate(): минус сопряженное комплексное чи 水
' Арифметические операторьт: " (унарный), +,_,
* °ператоры сравнения: ==» ! =,<‘>' >
потоксве紀 операторн: >>,<<:口 опжалуй-
пт нттагга и его методов, пожалу
более детального описания структуры класс
1 ° Р^Щ^йтесь к разд. 6-3.
// MComplex.h
// Класс комплексных чисел
#ifndef COMPLEX』
#define COMPLEX』
#include <iostream.h>
#include <math.h>
template <class T>
class Complex
private:
// поля данных : вещественная часть и мнимая часть
Т real;
Т imag;
public:
// конструкторы и деструктор
Complex();
Complex(Т);
Complex(Т, Т);
Complex(const Complex<T> &);
~Complex();
// методы
T realPart() const;
T imagPart() const; double magnitude() const; double argument() const; Complex<T> conjugate(); Complex<T> negate();
// арифметические операторы и операторы сравнения const Complex<T> & operator = (const Complex<T>&);
Complex<T> Complex<T> Complex<T> Complex<T> Complex<T>Complex<T> Complex<T> Complex<T> Complex<T> Complex<T>operator operator operator operator operatoroperator operator operator operator operator()const;
Complex<T> Complex<T> Complex<T> Complex<T> Complex<T>
Complex<T> Complex<T> Complex<T> Complex<T> Complex<T>
operator operator operator operator operator
operator operator operator operator operator
+= (const Complex<T>&);
=(const Complex<T>&);
= (const Complex<T>&);
/= (const Complex<T>&);
friend Complex<T> operator +
friend Complex<T> operator -
friend Complex<T> operator *
(const Complex<T>&, const Complex<T>&);
(const Complex<T>&, const Complex<T>&);
(const Complex<T>&, const Complex<T>&);
Complex<T> operator
(const Сотр1ех<Т>&л const Complex<T>&);
friend friend friend friend friend friend
int int int int int int
operator operator operator operator operator operator
< (const Complex<T>&, <=(const Complex<T>&, > (const Complex<T>&, >=(const Complex<T>&, ==(const Complex<T>&, !=(const Complex<T>&,
const const const const const const
Complex<T>&);
Complex<T>&);
Complex<T>&);
Complex<T>&);
Complex<T>&);
Complex<T>&);
// функции потокового ввода/вывода
friend istream & operator >> (istreamfe, Complex<T>&);
friend ostream & operator << (ostreamfe, const Complex<T>&);
〃 конструкторы, деструктор и конструктор копирования
template <class Т> Complex<T>::Complex() : real(T(0)), imag(T(0)) {} template <class T> Complex<T>::Complex(T r) : real(r), imag(T(0)) {}
template <class T> Complex<T>::Complex(T r,T i) : real(r),imag(i) {}
:real(c.real),:real(c.real),imag(c.imag) {}imag(c.imag) {}template <class T> Complex<T>::Complex(const Complex<T> &c)
:real(c.real),
:real(c.real),
imag(c.imag) {}
imag(c.imag) {}
template <class T>template <class T>Complex<T> : : "Complex () {}
template <class T>
template <class T>
// методы
template <class T> T Complex<T>::realPart() const { return real; }
template <class T> T Complex<T>::imagPart() const { return imag; }
template <class T> double Complex<T>::magnitude() const { return sqrt (double (real*real+imag*imag) ) ; }
;amp]_ate <class T> double Complex<T>: : argument () const return atan2 (double (imag) a double (real)) ; }
emplane <class T> Complex<T> Complex<T>::conjugate()
r®tum Complex<T> (real, imag = -imag) ; }
mpiate <class T> Complex<T> Complex<T>::negate()
return Complex<T>(real = -real, imag); }
^Р^метические операторы и операторы сравнения
『Plate <claSs Т>
LOnS^ с
3mplex<T> & Complex<T>::operator 二(const Comp工ex<T> &с)
real = с.real; imag = с.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 *th.is = *th.is + c; }
template <class T>
(const Complex<T> &c)(const Complex<T> &c)(const Complex<T> &c)(const Complex<T> &c)(const Complex<T> &c)(const Complex<T> &c)Complex<T> Complex<T>::operator -= { return. *th.is = *th.is - c; }
(const Complex<T> &c)
(const Complex<T> &c)
(const Complex<T> &c)
(const Complex<T> &c)
(const Complex<T> &c)
(const Complex<T> &c)
template <class T>
Complex<T> Complex<T>::operator *= { return *this = *this * c; }
template <class T>
Complex<T> Complex<T>::operator /= { return *th.is = *th.is / c; }
template <class T>
Complex<T> operator + (const Complex<T> &cl, const Complex<T> &c2)
{ return. Complex<T>(cl.real+c2.real, cl.imag+c2.imag); }
template <class T>
Complex<T> operator - (const Complex<T> &cl, const Complex<T> &c2)
{ return. Complex<T>(cl.real-c2.real, cl.imag-c2.imag); }
template <class T>
Complex<T> operator * (const Complex<T> &cl, const Complex<T> &c2)
return. Complex<T>(cl.real*c2.real - c1.imag*c2.imags cl.real*c2.imag + cl.imag*c2.real);
template <class T>
Complex<T> operator / (const Complex<T> &cls const Complex<T> 虹2) {
T modulus = c2.real * c2.real + c2.imag * c2.imag;
static T zero(0);
if (modulus == zero)
сеГГ << "Ошибка : нулевой делитель" << endl; return Complex<T>(zero,zero);
}rn Complex<T>((cl.imag * c2.imag + cl.real * c2.real)/modulus,
r°(cl.imag * c2.real - cl.real * c2.imag)/modulus);
&c2)&c2)template〈class T>
&c2)
&c2)
° t operator < (const Complex<T> &cl, const Complex<T> ^return (cl.real*cl.real+cl.imag*cl.imag <
Ic2.real*c2.real+c2.imag*c2.imag); }
template <class T>
jut operator <= (const Complex<T> &cl, const Complex<T> &c2)
{ return (cl<c2) I I (cl==c2); }
template <class T>
int operator > (const Complex<T> &cl, const Complex<T> &c2)
{ return (cl.real*cl.real+cl.imag*cl.imag >
c2.real*c2.real+c2.imag*c2.imag); }
template <class T>
int operator >= (const Complex<T> &cl, const Complex<T> &c2) { return. (cl>c2) I I (cl==c2); }
template <class T>
int operator == (const Complex<T> &cl, const Complex<T> &c2)
{ return (cl.real==c2.real) && (cl.imag==c2.imag); }
template <class T>
int operator ! = (const Complex<T> &cl, const Complex<T> &c2) { return !(cl==c2); }
〃 ПОТОКОВЫЙ ввод/вывод
static T zero(0); L,i; char c;s >> ws;c * s.peekO ; 迁(c==〉( >)c = s.get(); s >> r;S >> ws;static T zero(0); L,i; char c;s >> ws;c * s.peekO ; 迁(c==〉( >)c = s.get(); s >> r;S >> ws;:emplate <class T> istream & operator >> (istream &s, Complex<T> &z)
static T zero(0); L,i; char c;
s >> ws;
c * s.peekO ; 迁(c==〉( >)
c = s.get(); s >> r;
S >> ws;
static T zero(0); L,i; char c;
s >> ws;
c * s.peekO ; 迁(c==〉( >)
c = s.get(); s >> r;
S >> ws;
// удаляем лидирующие пробелы
// переход к следующему символу
// очистка ,(J
// чтение вещественной части
// удаляем хвостовые пробелы
с =
=s.peek();
// переход к следующему символу
if (с ==,,,)
с = s.get();
s >> i;
s >> ws;
с = s.реек();
else i = zero;
// чтение мнимой части
// мнимая часть отсутствует
if (с != >) s. clear(s.rdstateO | ios: :badbit);
else{s >> r;i = zero;z = Complex<T>(r,i); return s;else{s >> r;i = zero;z = Complex<T>(r,i); return s;// это - действительное число
else
{
s >> r;
i = zero;
z = Complex<T>(r,i); return s;
else
{
s >> r;
i = zero;
z = Complex<T>(r,i); return s;
// чтение вещественной части
// инициализгщия вещественной частз
}
template <class Т>
ostream & operator << (ostream &s, const Complex<T> &c) {
static T zero(0);
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
Класс Quaternion
Открытый интерфейс класса Quaternion:
Quaternion. (): конструктор по умолчанию.
Quaternion (Т,Т,Т,Т): конструктор.
sqr(): квадрат кватерниона.
conj ugate(): сопряженное к кватерниону.
inverse(): инверсия кватерниона.
magnitude (): модуль кватерниона.
арифметические операторы: -(унарный), +, -, *, /, ~
потоковые операторы: >>, <<
Для более детального описания структуры класса и его методов, по ста, обращайтесь к разд. 64
〃 Quatern』
def QUATERNION-H 嘗me QUATERNION.H
include jcludeinclude jclude<iostream.h><math.. h><iostream.h><math.. h>// для sqrt()// для sqrt()
include jclude
include jclude
<iostream.h>
<math.. h>
<iostream.h>
<math.. h>
// для sqrt()
// для sqrt()
templatetemplate<class T> class Quaternion.
template
template
{ private:
// поля данных
T r, i, j, к;
public:
// конструкторы
Quaternion.();
Quaternion.(Т, Т, Т, Т);
Quaternion(const Quaternion<T> &);
■"Quaternion.();
// операторы
const Quaternion<T> & operator = (const Quaternion<T> &);
Quaternion<T> Quaternion<T> Quaternion.<T> Quaternion.<T> Quaternion<T> Quaternion<T> Quaternion.<T>Quaternion<T> Quaternion<T> Quaternion.<T> Quaternion.<T> Quaternion<T> Quaternion<T> Quaternion.<T>operator + (const Quaternion<T> &);
Quaternion<T> Quaternion<T> Quaternion.<T> Quaternion.<T> Quaternion<T> Quaternion<T> Quaternion.<T>
Quaternion<T> Quaternion<T> Quaternion.<T> Quaternion.<T> Quaternion<T> Quaternion<T> Quaternion.<T>
operator - (const Quaternion.<T> &);
operator - () const;
operator * (const Quaternion<T> &);
operator * (T);
operator / (const Quaternion<T> &);
operator ' () const;
// методы
Quatern.ion.<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> friend istream & operator >> (istreamfe, Quaternion<T>&);
加Plate <class T> Quaternion.<T>: : Quaternion() :r(T(0)), i(T(0)), j(T(O)), k(T(0)) {}
template <class Т> Quaternion<T>: :Quaternion(Т rl, T il, T jj :r(rl) , i(il) , j (jl) , k(kl) {}' 抬)
template<class T>Quaternion.<T>: : Quaternion.(const Quaternion<T>&
:r(arg.r) , i(arg.i) , j(arg.j), k(arg.k) {}敢且)
template <class T> Quaternion<T>::"QuaternionO {}
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.rs 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. Quatern.ion.<T>(-r,-i3-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*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>
Quatern.ion.<T> Quaternion.<T>: : operator / (const Quaternion<T>& ^g' { return. *this * arg.inverseO ; }
// нормализация кватерниона template <class T>
Quaternion.<T> Quaternion<T>: : operator ~ () const
Quaternion<T> result;
doUble length = magnitude();
result.r = r/length; result.i = i/length;
result.j = j/length; result.к = k/length;
return result;
iplate <class T> Quaternion<T> Quaternion<T>::sqr()
Quaternion<T> result;
T temp;
T two = T(2);
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 Quatern.ion<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>
;Mrea1n & operator << (ostreamfe s, const Quaternion.<T>& arg)
s « "(" << arg.r « "," « arg.i « H,"
<< arg.j << "," << arg.к << ")";
} return s;
template <class Т>
istreamfc operator >> (istreamfe s, Quaternion<T>& arg)
s >> arg.r >> arg.i >> arg.j >> arg.к; return s;
#endif
Класс 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);
};
/ арифметические операторы
-- () const ;
Derive<T> operator Derive<T>
Derive<T> Derive<T> Derive<T>
operator operator operator operator
+=
*=
/=
(const (const (const (const
Derive<T>&) Derive<T>&) Derive<T>&) Derive<T>&)
friend friend friend friend friend friend friend friend friend
Derive<T> Derive<T> Derive<T> Derive<T> Derive<T> Derive<T> Derive<T>
operator + operator - operator * operator /
(const Derive<T>&, (const Derive<T>&, (const Derive<T>&, (const Derive<T>&,
exp(const Derive<T>&); sin(const Derive<T>&);
cos(const Derive<T>&);
T df(const Derive<T>&);
const Derive<T>&); const Derive<T>&); const Derive<T>&); const Derive<T>&);
ostream& operator << (ostream&, const Derive<T>&);
template <class T> Derive<T>::Derive() : u(T(0)), du(T(l)) {}
template <class T> Derive<T>::Derive(const T u) : u(v), du(T(0)) {}
template <class T>
Derive<T>::Derive(const T v, const T dv)
u(u), du(du) {}
template <class T>
Derive<T>::Derive(const Derive<T> &r) : u(r.u), dn(r.du) {}
template <class T> void Derive<T>::set(const T v) { n = v; }
template <class T> Derive<T> Derive<T>::operator - () const £ return Derive<T>(-n,-du); }
template <class T>Derive<T>Derive<T>::operator+=(const Derive<T>& r) £ return *this = *this + r; }
:empiate <class T>Derive<T>Derive<T>::operator~=(const Derive<T>& r) f return *this = *this - r; }
template{ return
<class T>Derive<T>Derive<T>::operator*=(const Derive<T>& r)
*th.is = *this * r; }
;®mp]_ate <class T>Derive<T>Derive<T>::operator/=(const Derive<T>& r) return *th.is - *this / r; }
"mplata <class T>
Derive<T> operator + (const Derive<T>& x, const Derive<T>& y) { return Derive<T>(x.u + у.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, у.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
Класс Vector
Открытый интерфейс класса Vector:
Vector (): конструктор по умолчанию.
Vector(int): конструктор.
Vector(int ,Т): конструктор.
length(): размерность вектора.
resize (int n.): приведение вектора к размерности п.
resize(int n,T v): приведение вектора к размерности п и заполг оставшихся элементов вектора значением v.
eSet (int n): установка вектора к размерности n.
'feSet(int n Д v): установка вектора к размерности п и инициализация
* цемент。》вектора значением v.
арйфметические операторы: + (унарный)? -(унарный), +, 一, *, /, +=, -=, *=, операторы сравнения: ==, !=
* оператор взятия индекса:[]
* внутреннее произведение: I
.прямое произведение: %
.потоковые операторы: >>, <<
вспомогательные функции из VecNorm.h:
.norml(const Vector^): 1-норма вектора.
norm2(const Vector^): 2-норма вектора.
norml(const Vectors): бесконечная норма вектора.
normalize (const Vector&): нормализация вектора.
Для более детального описания структуры класса и его методов, пожалуйста, обращайтесь к разд. 6.6.
// Vector .h.
#ifndef MVECTOR_H
#define MVECTOR_H
#include <iostream.h>
#include <string.h>
#include <math..h.>
#include <assert .h>
/7 опережающее объявление template <class T> class Matrix;
〃 определение класса Vector
;emp]_ate <class T> class Vector
private:
// поля данных
int size;
T *data;
Public:
// конструкторы
Vector ();
Vector(int);
Vector(int, T);
Vector(const Vector<T>&);
~Vector();
〃 методы
Т & operator [] (int) const; int length.() 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>
operator
+
()const
Vector<T>
operator
()const
Vector<T>
operator
+=
(const
Vector<T>&);
Vector<T>
operator
(const
Vector<T>&);
Vector<T>
operator
*=
(const
Vector<T>&);
Vector<T>
operator
/=
(const
Vector<T>&);
Vector<T>
operator
+
(const
Vector<T>&)
const;
Vector<T>
operator
(const
Vector<T>&)
const;
Vector<T>
operator
*
(const
Vector<T>&)
const;
Vector<T>
operator
/
(const
Vector<T>&)
const;
Vector<T>
operator
+=
(T);
Vector<T>
operator
(T);
Vector<T>
operator
*=
(T);
Vector<T>
operator
/=
(T);
Vector<T>
operator
+
(T) const;
Vector<T>
operator
(T) const;
Vector<T>
operator
*
(T) const;
Vector<T>
operator
/
(T) const;
// внутреннее/скалярное// произведение/ / прямое произведение// внутреннее/скалярное// произведение/ / прямое произведениеT operator I (const Vector<T>&);
// внутреннее/скалярное
// произведение
/ / прямое произведение
// внутреннее/скалярное
// произведение
/ / прямое произведение
Vector<T> operator % (const Vector<T>&)
// функхщи потокового ввода/вывода
friend class Matrix<T>;
friend ostreamfe operator << (ostream&, const Vector<T>&);
friend istreamfe operator >> (istream&, Vector<T>&);
// реализация класса Vector
f[n])f[n])template <class T> Vector<T>::Vector() : size(0), data(NULL) {}
f[n])
f[n])
template <class T> Vector<T>::Vector(int n) : size(n),data(new { assert(data != NULL); }
/ te <class T> Vector<T>::Vectcr(int n, T value)
*'“P :ize(n),data (new T[n])
aSSert(data != NULL);
<class T> Vector<T>::Vector(const Vector<T> &v)
'° . size(v.size) , data(new T[v.size])
< 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> int Vector<T>::length() const
{ return size; }
template <class T> void Vector<T>::resize(int length)
{
int i;
T zero(0);
T *newData = new T[length]; assert(newData != NULL);
if (length <= size)
for (i=0; i<length; i++) newData[i]二 data[i];
else
for (i=0; i<size; i++) newData[i] = data[i];
for (i=size; i<length; i++) newData[i] = zero;
delete □ data;
size = length;
} data. = newData;
:mp 1ate <class T> void Vector<T>::resize(int length, T value)
T *newData = new T[length]; assert(newData != NULL);
if (length <= size)
for (i=0; i<length; i++) newDatati] = data[i]; else
for (i=0; i<size; i++) newDatati] = data[i];
for (i=size; Klength; i++) newDatati] = value;
delete [] data;
size = length;
data = newData;
template <class T> void Vector<T>::reset(int length)
T zero(0);
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)
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; i<v.size; i++) data[i]二 v.data[i];
return *this;
template <class T> const Vector<T>& Vector<T>::operator = (T va
for (int i=0; Ksize; i++) data[i] = value;
<class
T> Vector<T> Vector<T>::operator +
()const
tempi®
;return
*this;
}
<class
T> Vector<T> Vector<T>::operator -
()const
template { return
*this ;
* T(-l); }
templat;
Vector。
<class T>
> Vector<T>::operator += (const Vector<T>&
V)
lSert(size==v.size);
data[i] += v.data[i];
(int i=0; i<size; i++)
return *th:LS;
for
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++) return *this;
}
data[i] -= v.data[i];
template <class T>
Vector<T> Vector<T>: : operator
*= (const Vector<T>& v)
assert(size==v.size);
for (int i=0; i<size; i++) return *this;
data[i] *= v.data[i];
template <class T>
;ector<T> Vector<T>::operator
/= (const Vector<T>& v)
assert(size==v.size);
'or (int i=0; Ksize; i++) } return *this;
data[i] /= v.data[i];
V;*te <dass T>
{ ct°r<T> Vector<T>::operator
^ector<T> result(*this);
)return result += v;
+ (const Vector<T>& v) const
template <class Т>
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
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
for (int i=0; return *this;
}
T> Vector<T> Vector<T>::operator += (T c)
i<size; i++)
data[i] += c;
template <class
T> Vector<T>
Vector<T>::operator -= (T c)
for (int i=0; return *this;
template <class
i<size; i++)
T> Vector<T>
data[i]-二 c;
Vector<T>::operator *= (T c)
for (int i=0; return *this;
template <class
Ksize ; i++)
T> Vector<T>
data[i] *= c;
Vector<T>::operator /= (T c)
for (int i=0; return *this;
template <class
Ksize; i++)
T> Vector<T>
data[i] /= c;
Vector<T>::operator + (T c) cons
Vect。:Vect。:|Г<Т> result(*this);|Г<Т> result(*this);returnreturnresult += c;result += c;…e <class T> Vector<T> ME…e <class T> Vector<T> MEVector<T>::operator - (T c)Vector<T>::operator - (T c)constconst
Vect。:
Vect。:
|Г<Т> result(*this);
|Г<Т> result(*this);
return
return
result += c;
result += c;
…e <class T> Vector<T> ME
…e <class T> Vector<T> ME
Vector<T>::operator - (T c)
Vector<T>::operator - (T c)
const
const
ctor<T> result(*this);
return }
template <class T> Vector<T>template <class T> Vector<T>Vector<T>::operator * (T c)Vector<T>::operator * (T c)constconst
template <class T> Vector<T>
template <class T> Vector<T>
Vector<T>::operator * (T c)
Vector<T>::operator * (T c)
const
const
Vector<T> result(*this); return result *= c;
template <class T> Vector<T>template <class T> Vector<T>Vector<T>::operator / (T c)Vector<T>::operator / (T c)constconst
template <class T> Vector<T>
template <class T> Vector<T>
Vector<T>::operator / (T c)
Vector<T>::operator / (T c)
const
const
Vector<T> result(*this); return result /= c;
template { returntemplate { return<class T> v+c; }<class T> v+c; }Vector<T>Vector<T>operator + (T c, constoperator + (T c, constVector<T>&Vector<T>&v)v)template { returntemplate { return<class T>-v+c; }<class T>-v+c; }Vector<T>Vector<T>operator - (T c, constoperator - (T c, constVector<T>&Vector<T>&v)v)template { returntemplate { return<class T>V*c; }<class T>V*c; }Vector<T>Vector<T>operator * (T c, constoperator * (T c, constVector<T>&Vector<T>&v)v)template {template {<class T><class T>Vector<T>Vector<T>operator / (T c, constoperator / (T c, constVector<T>&Vector<T>&v)v)
template { return
template { return
<class T> v+c; }
<class T> v+c; }
Vector<T>
Vector<T>
operator + (T c, const
operator + (T c, const
Vector<T>&
Vector<T>&
v)
v)
template { return
template { return
<class T>
-v+c; }
<class T>
-v+c; }
Vector<T>
Vector<T>
operator - (T c, const
operator - (T c, const
Vector<T>&
Vector<T>&
v)
v)
template { return
template { return
<class T>
V*c; }
<class T>
V*c; }
Vector<T>
Vector<T>
operator * (T c, const
operator * (T c, const
Vector<T>&
Vector<T>&
v)
v)
template {
template {
<class T>
<class T>
Vector<T>
Vector<T>
operator / (T c, const
operator / (T c, const
Vector<T>&
Vector<T>&
v)
v)
Vector<T> result(v.length());
} //} //%r (int i=0; i<result.length(); i++) result[i] = c/v[i]; return result;
} //
} //
скалярное произведение
opiate <class T> T Vector<T>::operator I (const Vector<T>& v)
assert(size == v.size);
T result(0);
"r (int i=0; i<size; i++) result = result + data[i]*v.data[i]; } return result;
// векторное произведение template <class Т>Vector<T> Vector<T>::operatorassert(size == 3 && v.size Vector<T> result (3);result.data[0] = data[l] * result.data[l] = v.data[0] result.data[2] = data[0] * return result;// векторное произведение template <class Т>Vector<T> Vector<T>::operatorassert(size == 3 && v.size Vector<T> result (3);result.data[0] = data[l] * result.data[l] = v.data[0] result.data[2] = data[0] * return result;% (const Vector<T>& v)==3);v.data[2] - v.data[l] * data[2]. * data[2] - data[0] * v.data[2]• v.data[l] - v.data[0] * data[l] •% (const Vector<T>& v)==3);v.data[2] - v.data[l] * data[2]. * data[2] - data[0] * v.data[2]• v.data[l] - v.data[0] * data[l] •
// векторное произведение template <class Т>
Vector<T> Vector<T>::operator
assert(size == 3 && v.size Vector<T> result (3);
result.data[0] = data[l] * result.data[l] = v.data[0] result.data[2] = data[0] * return result;
// векторное произведение template <class Т>
Vector<T> Vector<T>::operator
assert(size == 3 && v.size Vector<T> result (3);
result.data[0] = data[l] * result.data[l] = v.data[0] result.data[2] = data[0] * return result;
% (const Vector<T>& v)
==3);
v.data[2] - v.data[l] * data[2]. * data[2] - data[0] * v.data[2]• v.data[l] - v.data[0] * data[l] •
% (const Vector<T>& v)
==3);
v.data[2] - v.data[l] * data[2]. * data[2] - data[0] * v.data[2]• v.data[l] - v.data[0] * data[l] •
// равенств。
template <class T>
int operator == (const Vector<T>& u, const Vector<T>& v)
if (u.lengthC) != v.length()) return 0;
for (int i=0; i<u.length(); 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)
int last num = v. lengthO ;
for (int i=0; Klastnum; i++) s << " [" « v[i] « ”]" << endl; return s;
template <class T> istream &int i, num;s.clear();//s >> num;〃if(!s.good()) return s; // v. resize (num) ;//for (i=0; i<num; i++) {template <class T> istream &int i, num;s.clear();//s >> num;〃if(!s.good()) return s; // v. resize (num) ;//for (i=0; i<num; i++) {operator >> (istreamfe s, Vector<T>& v)
template <class T> istream &
int i, num;
s.clear();//
s >> num;〃
if(!s.good()) return s; // v. resize (num) ;//
for (i=0; i<num; i++) {
template <class T> istream &
int i, num;
s.clear();//
s >> num;〃
if(!s.good()) return s; // v. resize (num) ;//
for (i=0; i<num; i++) {
установка статуса потока в "готов"
читаем размерность вектора
если не получаем целого числа, ухоД^1d переустанавливаем размерность веКТ°Р
s » v[i];if (! s.goodO)s » v[i];if (! s.goodO)// считываем компоненты вектора
s » v[i];
if (! s.goodO)
s » v[i];
if (! s.goodO)
' s.clear(s.rdstate() I ios::badbit); return s;
Return s;
〃 yecNorm.h
;/ векторные нормы
#ifndef MVECNORM-H
#define MVECNDRM』
#inclnde <iostream.h>
断ncl口de <ma"th.h>
template <class T> T norml(const Vector<T>& v)
T result(0);
for (int i=0; i<v.length(); i++) result = result + abs(v [i]);
return result;
}
double norml(const Vector<double>& v)
{
double result(0);
for (int i=0; i<v.length(); i++) result = result + fabs(v[i]);
} return result;
:mplate <class T> double norra2(const Vector<T>&
T result(0);
‘°工(int i=0; i<v.length(); i++) result = result + v[i]*v[i];
} "turn sqrt(double(result));
template <class Т> Т norral(const Vector<T>& v)
T maxltem(abs(v[0])) , temp;
for (int i=l; i<v.length(); i++)
temp = abs(v[i]);
if (temp > maxItera) maxItem = temp;
return maxlteni;
double norml(const Vector<double>& v)
double maxIteiu(fabs(v[O])), temp;
for (int i=l; Kv.lengthO; i++) {
temp = fabs(v[i]);
if (temp > max It era) max It era = temp;
return raaxltem;
template <class T> Vector<T> normalize(const Vect {
Vector<T> result(v.length());
double length = norm2(v);
for (int i=0; i<v.length(); i++) result [i] = v[i]/length;
return result;
#endif
Класс Matrix
Открытый интерфейс класса Matrix:
Matrix(): конструктор по умолчанию.
Matrix(int, int): конструктор.
Matrix(int, int T): конструктор.
Matrix(const Vector<T>&): конструктор.
identity(): создать единичную матрицу.
transposeO: транспонирование матрицы.
/ rSe(): обращение матрицы.
・вычисление следа матрицы.
' 十erminant (): вычисление детерминанта матрицы.
* d° s(): количество строк матрицы.
* "olsO: количество столбцов матрицы.
с ize(jnt, int): приведение матрицы к новым размерностям.
':;jze(int, int, Т): приведение матрицы к новым размерностям и ини-
йаЛизация оставшихся элементов матрицы.
:ес(const Matrix<T>&): оператор векторизации.
* kron(const Matrix<T>&, const Matrix<T>&): произведение Кронекера. fjll(T v): заполнение матрицы значением v.
.арифметические операторы: +(унарный), -(унарный), +,*, /, +=,
=,*=, I-
.оператор взятия вектор-строки:()
.оператор взятия вектор-столбца:[]
потоковые операторы: >>, «
Вспомогательные функции из MatNorra.h:
norml (const Matrix^): первая норма матрицы.
norml (const Matrix&): бесконечная норма матрицы.
погшН( const Matrix&): норма Г ильберта—Шмидта матрицы.
Для более детального описания структуры класса и его методов, пожалуйста, обращайтесь к разд. 67
〃 Matrix. h
#ifndef MATRIX_H
define MATRIX.H
'include <iostreciin. h>
^include <math.h>
/include <assert .h>
^include "Vector .h"
// определение класса Matrix
:'"plate <class T> class Matrix protected:
// поля данных
int rowNum, colNum;
Vector<T> *mat;
Public:
// конструкторы
Matrix();
Matrix(int, int);
Matrix(int, int, T);
Matrix(const Vector<T>&);
Matrix(const Matrix<T>&); ~Matrix();
// методы
Vector<T>& operator [] (int) const;
Vector<T> operator () (int) const;
Matrix<T> identity();
Matrix<T> transposeO const;
Matrix<T> inverse() const;
T trace() 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> operator + () const;
Matrix<T>
operator
-
()consl
Matrix<T>
operator
+=
(const
Matrix<T>&);
Matrix<T>
operator
(const
Matrix<T>&);
Matrix<T>
operator
*=
(const
Matrix<T>&);
Matrix<T>
operator
+
(const
Matrix<T>&) const;
Matrix<T>
operator
-
(const
Matrix<T>&) const;
Matrix<T>
operator
*
(const
Matrix<T>&) const;
Vector<T>
operator
*
(const
Vector<T>&) const;
Matrix<T>
operator
+=
(T);
Matrix<T>
operator
(丄);
Matrix<T>
operator
*=
(丄丿;
Matrix<T>
operator
/=
k 1 / ;
Matrix<T>
operator
+
(T) const;
Matrix<T>
operator
(Т) const;
Matrix<T>
operator
*
(Т) const;
Matrix<T>
operator
/
(Т) const;
friend Vector<T> vec(const Matrix<T>&);
friend Matrix<T> kron(const Matrix<T>&, const Matrix<T>&); friend ostream 也 operator << (ostream&, const Matrix<T>&)>
к ^riend istream & operator » (istream &, Matrix<T>&);
);加13aци笈 класса Matrix
// 产伏 <class T> Matrix<T>::Matrix()
temP’jowNuml。),colNum(O) , mat (NULL) {}
te <class T> Matrix<T>::Matrix(int r, int c)
*'叩;owNum(r) , colNum(c), mat (new Vector<T> M)
〈assert(mat != NULL);
for (int i=0; i<r; i++) mat[i].resize(c);
template <class T> Matrix<T>: :Matrix(int r, int с, T 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] .resize(l ,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; i<m.rowNum; i++) mat[i] = m.mat[i];
『迎plate <class T> Matrix<T>::"Matrix()
} delete [] mat;
'^Plate <class T> Vector<T> & Matrix<T>::operator □
{(int index) const
Assert(index>=0 && index<rowNum);
} Aturn mat [index];
template <class Т> Vector<T> Matrix<T>::operator () (int index) const {
assert(index>=0 && index<colNum);
Vector<T> result(rowNum);
for (int i=0; KrowNum; 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(0);
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, I(rowNum, colNum);
T cO(B.trace()), cl;
int i;
dentity();
for (i=2; i<rowNum; 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;
}
十0 <class T> Т Matrix<T>::trace() const
«ММ
(aSSert (rowNum == colNum);
T result(0);
for (int i=0; KrowNum; i++) result += mat[i] [i]; return result;
,символьный детерминант
"mplate <class T> T Matrix<T>::determinant() const
〔assert (rowNiun==colMum);
Matrix<T> B(*this), KrowNum, colNum, T(0));
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);
c = 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;
:mpiate <class T>:mpiate <class T>void Matrix<T>::resize(int rvoid Matrix<T>::resize(int rint c)int c)
:mpiate <class T>
:mpiate <class T>
void Matrix<T>::resize(int r
void Matrix<T>::resize(int r
int c)
int c)
St i;
Vector<t> *newMat = new Vector<T>[r]; assert(newMat != N
;f (r<=rowNum)
for (i=0; i<r; i++) {
(mat+i) -> resize(c);
newMat[i] = mat [i];
else {
for (i=0; KrowNum; 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 с, T 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++)
for (int j=0; j<colNum; j++) mat[i][j] = value;
^late <class T>
仅“厶 Matrix<T>& Matrix<T>::operator = (const Matrix<T>& m)
辻(this == &m) return *th.is;辻(this == &m) return *th.is;delete rowNumdelete rowNum[]mat;= m.rowNum; colNum 二 m.colMum;[]mat;= m.rowNum; colNum 二 m.colMum;mat format for=new=newVector<T>[m.rowNum] ; assert(mat != NULL);Vector<T>[m.rowNum] ; assert(mat != NULL);(int i=0; Km.rowNum; i++) mat[i] = m.mat[i];(int i=0; Km.rowNum; i++) mat[i] = m.mat[i];return *this;return *this;
辻(this == &m) return *th.is;
辻(this == &m) return *th.is;
delete rowNum
delete rowNum
[]mat;
= m.rowNum; colNum 二 m.colMum;
[]mat;
= m.rowNum; colNum 二 m.colMum;
mat for
mat for
=new
=new
Vector<T>[m.rowNum] ; assert(mat != NULL);
Vector<T>[m.rowNum] ; assert(mat != NULL);
(int i=0; Km.rowNum; i++) mat[i] = m.mat[i];
(int i=0; Km.rowNum; i++) mat[i] = m.mat[i];
return *this;
return *this;
template <class T>
gust Matrix<T>& Matrix<T>::operator = (T value)
for (int i=0; i<rowNum; i++) mat[i] = value; return *this;
}
()const()consttemplate <class T> Matrix<T> Matrix<T>::operator + return *this;
()const
()const
template <classtemplate <classT> Matrix<T> Matrix<T>::operator -T> Matrix<T> Matrix<T>::operator -()const()constreturn *thisreturn *this* T(-l);* T(-l);template <classtemplate <classT>T>Matrixs Matrix<T>: :operator +=Matrixs Matrix<T>: :operator +=(const(constMatrix<T>&Matrix<T>&m)m)
template <class
template <class
T> Matrix<T> Matrix<T>::operator -
T> Matrix<T> Matrix<T>::operator -
()const
()const
return *this
return *this
* T(-l);
* T(-l);
template <class
template <class
T>
T>
Matrixs Matrix<T>: :operator +=
Matrixs Matrix<T>: :operator +=
(const
(const
Matrix<T>&
Matrix<T>&
m)
m)
} return *this = *this + m;
;®mpiate <class T>,S»x<T> Matrix<T>: : operator -=;®mpiate <class T>,S»x<T> Matrix<T>: : operator -=(const(constMatrix<T>&Matrix<T>&m)m)
;®mpiate <class T>
,S»x<T> Matrix<T>: : operator -=
;®mpiate <class T>
,S»x<T> Matrix<T>: : operator -=
(const
(const
Matrix<T>&
Matrix<T>&
m)
m)
} return *this = *this - m;
丄ate <class T>ix<T> Matrix<T>::operator *=丄ate <class T>ix<T> Matrix<T>::operator *=(const(constMatrix<T>&Matrix<T>&m)m)
丄ate <class T>
ix<T> Matrix<T>::operator *=
丄ate <class T>
ix<T> Matrix<T>::operator *=
(const
(const
Matrix<T>&
Matrix<T>&
m)
m)
return *this = *this * m;
template <class T>Matrix<T> Matrix<T>::operator + {assert(rowNum == m.rowNum 也也Matrix<T> result(*this);for (int i=0; i<rowNum; i++) return result;template <class T>Matrix<T> Matrix<T>::operator + {assert(rowNum == m.rowNum 也也Matrix<T> result(*this);for (int i=0; i<rowNum; i++) return result;(const Matrix<T>& m)(const Matrix<T>& m)colNum == m.colNum);colNum == m.colNum);result [i] += m[i];result [i] += m[i];constconsttemplate <class T>Matrix<T> Matrix<T>::operator - {assert(rowNum == m.rowNum 也也Matrix<T> result(*this);for (int i=0; i<rowNum; i++) return result;template <class T>Matrix<T> Matrix<T>::operator - {assert(rowNum == m.rowNum 也也Matrix<T> result(*this);for (int i=0; i<rowNum; i++) return result;(const Matrix<T>& m)(const Matrix<T>& m)colNum == m.colNum);colNum == m.colNum);result [i] -= m[i];result [i] -= m[i];constconst
template <class T>
Matrix<T> Matrix<T>::operator + {
assert(rowNum == m.rowNum 也也
Matrix<T> result(*this);
for (int i=0; i<rowNum; i++) return result;
template <class T>
Matrix<T> Matrix<T>::operator + {
assert(rowNum == m.rowNum 也也
Matrix<T> result(*this);
for (int i=0; i<rowNum; i++) return result;
(const Matrix<T>& m)
(const Matrix<T>& m)
colNum == m.colNum);
colNum == m.colNum);
result [i] += m[i];
result [i] += m[i];
const
const
template <class T>
Matrix<T> Matrix<T>::operator - {
assert(rowNum == m.rowNum 也也
Matrix<T> result(*this);
for (int i=0; i<rowNum; i++) return result;
template <class T>
Matrix<T> Matrix<T>::operator - {
assert(rowNum == m.rowNum 也也
Matrix<T> result(*this);
for (int i=0; i<rowNum; i++) return result;
(const Matrix<T>& m)
(const Matrix<T>& m)
colNum == m.colNum);
colNum == m.colNum);
result [i] -= m[i];
result [i] -= m[i];
const
const
template <class T>
(const Matrix<T>& m)(const Matrix<T>& m)constconstMatrix<T> Matrix<T>::operator * {
(const Matrix<T>& m)
(const Matrix<T>& m)
const
const
assert(colNum == m.rowNum);
Matrix<T> result(rowNum, m.colNum, T(0));
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;
constconsttemplate <class T>
const
const
Vector<T> Matrix<T>::operator * (const Vector<T>& v) {
assert (colNum == v.lengthO);
Vector<T> result(rowNum);
// используется внутреннее произведение I
for (int i=0; i<rowNum; i++) result [i] = (mat[i] | v); return result;
(T c)(T c)template <class T> Matrix<T> Matrix<T>::operator +=
(T c)
(T c)
ass,ass,ert(rowNum == colNum);
ass,
ass,
*this;*this;田urn田urn:(int i=0; KrowNum; i++) mat[i][i] += c
*this;
*this;
田urn
田urn
r “ <class T> Matrix<T> Matrix<T>::operator -= (T c) tempi”
{ aSsert(rowNum == colNum);
for (int i=0; KrowNum; i++) mat[i] [i] -= c;
return *this;
template <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 +template <class T>Matrix<T> Matrix<T>::operator +(T(Tvalue)value)constconst
template <class T>
Matrix<T> Matrix<T>::operator +
template <class T>
Matrix<T> Matrix<T>::operator +
(T
(T
value)
value)
const
const
assert(rowNum == colNum);
Matrix<T> result(*this); return result += value;
template <class T>"atrix<T> Matrix<T>::operator -template <class T>"atrix<T> Matrix<T>::operator -(T(Tvalue)value)constconst
template <class T>
"atrix<T> Matrix<T>::operator -
template <class T>
"atrix<T> Matrix<T>::operator -
(T
(T
value)
value)
const
const
assert(rowNum == colNum);
Matrix<T> result(*this);
} return result -= value;
Hate <class T> ix<T> Matrix<T>::operator *Hate <class T> ix<T> Matrix<T>::operator *(T(Tvalue)value)constconst
Hate <class T> ix<T> Matrix<T>::operator *
Hate <class T> ix<T> Matrix<T>::operator *
(T
(T
value)
value)
const
const
^atrix<T> result(*this); } return result *= value;
template <class Т>
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)
jat sizel size2 i, j, Matrix<T>jat sizel size2 i, j, Matrix<T>= s.rowNum * m.rowNmn, = s.colNum * m.colNum, k, p;
jat sizel size2 i, j, Matrix<T>
jat sizel size2 i, j, Matrix<T>
result(sizel, size2);
for (i=0; i<s.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> ostreainft operator << (ostreamfe s,
{const Matrix<T>& m)
int t = m.cols()-1;
for (int i=0; i<m.rows(); i++) {
s << ” [";
for (int j=0; j<t; j++) s « m[i] [j] « " *';
s << m[i] [t] << '*]" « endl;
}
} return s;
;*mplate <class T> istreamfe operator >> (istreamfe s, Matrix<T>& m)
int i, j, numl, num2;
// // // // // //// // // // // //если получили не целое, уходам чтение числа столбцовесли получили не целое, уходам чтение числа столбцовУХодим соответств 工УХодим соответств 工
// // // // // //
// // // // // //
если получили не целое, уходам чтение числа столбцов
если получили не целое, уходам чтение числа столбцов
УХодим соответств 工
УХодим соответств 工
s.clear();
s >> numl;
if (! s.good()) return s;
s >> num2;
if (! s.goodO) return s;
m.resize(niiml,num2);
for (i=0; i<numl; i++)
for (j=0; j<num2; j++)
s >> m[i] [j];
if (! s.goodO)
установка статуса потока чтение числа строк
если получили не целое приводам размерности в
s.cleax(s.rdstateO I ios: :badbit);
return s; }
return s;
#endif
// MatNorm.h
/ / матричные нормы
#ifndef MATN0RM„H
#define MATN口RM_H
#include#include#include#include#include#include#include#include<iostreciin.h.>
#include
#include
#include
#include
#include
#include
#include
#include
<math.. h> "Vector .h." "VecNorm.h"
template <class T> T norml(const Matrix<T>& m) {
T max工tem(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(0);
for (j=0; j<m.rows(); j++)
temp += abs(m[j] [i]);
if (temp > maxItem) maxltem = temp;
“ <class T> Т norml(const Matrix<T> &m) “ate
{ T maxitem(norml (m[0]));
r (int i=l; Km.rowsO ; i++)
if (norml (m[il) > maxltem) maxltem = norml(m[i]); return, maxltem;
template <class T> T normH(const Matrix<T> &m) { return sqrt((m*(m. transpose())) . trace());
#endif
Класс Array
Открытый интерфейс класса Array 1:
Array 1 (int 二 0): конструктор.
ArrayKint, T): конструктор.
resize (int) : изменение размера одномерного массива.
resize (int, T v) : изменение размера одномерного массива и инициализация оставшихся элементов значением v.
size(int = 0): размер массива.
арифметические операторы: +, -, *, +=, -=, *=
оператор взятия индекса:[]
потоковый оператор: <<
Открытый интерфейс класса Аггау2:
Array2(int = 0, int = 0): конструктор.
Array2(int, int s Т): конструктор.
resize(int, int): изменение размеров двумерного массива.
resize (int, int э Т v): изменение размеров двумерного массива и иници- ^изация оставшихся элементов значением v.
size(int = 0); размеры массива.
аРифметические операторы: +,*, +=, -=, *=
оператор взятия индекса:[]
п°токовый оператор: <<
кРЬ1тый интерфейс класса АггауЗ:
Array3(int = 0, int = 0, int = 0): конструктор.
Array3(int, int, int, Т): конструктор.
"size(int, int, int): изменение размеров трехмерного массива.
resize (int, int, int, T v): изменение размеров трехмерного ма инициализация оставшихся элементов значением v.*в@
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』
#include <iostream.h>
#include <assert.h>
template <class T>
class Array1
private:
// поля данных int n_data;
T *data;
public:
// конструкторы
ArrayKint 二 0);
Array1(int, T);
Array1(const Arrayl<T>&);
"ArraylO ;
// методы
void resize(int);
void resize(int, T);
Т & operator [] (int) const; int size(int = 0) const;
〃 арифметические операторы
const Arrayl<T>& operator 二(const Arrayl<T>&); const Arrayl<T>& operator = (T);
Arrayl<T> operator *=
(T);
Array1<T> operator +=
(const Arrayl<T>&);
Arrayl<T> operator -=
(const Arrayl<T>&);
Array1<T> operator +
(const Arrayl<T>&);
Array1<T> operator -
(const Arrayl<T>&);
// операторы потокового ввода/вывода
friend ostrecim & operator << (ostream&, const Arrayl<T>^
}•// конец объявления класса Array1
// конструкторы, деструкторы и конструктор копирования
template <class Т>
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> Arrayl<T>::Array1(const Arrayl<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];
:mplate <class T> Arrayl<T>::~Arrayl() } delete [] data;
〃 методы
fmplate <class T> void Arrayl<T>::resize(int n)
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]; 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 Т> Т& Arrayl<T>::operator [] (int i) const
assert(i >= 0 && i < n„data);
return data[i];
template <class T> int Arrayl<T>::size(int index)
assert(index == 0);
return n.data;
template <class T>
const Arrayl<T>& Arrayl<T>::operator 二(const Array1<T>& v)
if (this == &v) return *this;
if (n_data != v.n^data)
delete [] data;
n„data = v.n.data;
data = new T[v.n_data]; assert(data != NULL);
for(面 i=0;改t皿11 *this;for(面 i=0;改t皿11 *this;i<v.n_datai<v.n_datai++) data[i] = v.data[i];i++) data[i] = v.data[i];
for(面 i=0;改t皿11 *this;
for(面 i=0;改t皿11 *this;
i<v.n_data
i<v.n_data
i++) data[i] = v.data[i];
i++) data[i] = v.data[i];
(T num)(T num)plate <class T> const Arrayl<T>& Array1<T>::operator
(T num)
(T num)
Wr (int i=0; i<n_data; i++) data[i] = num; return *this;
}
template <class T> Arrayl<T> Arrayl<T>: : operator *= (T num)
〔for (int i=0; i<n_data; i++) data[i] *= num;
return *this;
}
template <class T>
Arrayl<T> Array 1<T>: : operator += (const Arrayl<T>& v) { return *this = *this + v; }
template <class T>
(const Arrayl<T>& v)(const Arrayl<T>& v)Arrayl<T> Array 1<T>: : operator -= { return *this = *this - v; }
(const Arrayl<T>& v)
(const Arrayl<T>& v)
template <class T>
Arrayl<T>& v)Arrayl<T>& v)= data[i] + v.data[i];= data[i] + v.data[i];Arrayl<T> Arrayl<T>: :operator + (const {
Arrayl<T>& v)
Arrayl<T>& v)
= data[i] + v.data[i];
= data[i] + v.data[i];
assert(n_data==v.n_data);
Arrayl<T> temp(n_data);
for (int i=0; i<n_data; i++) temp[i] return temp;
template <class T>
;"aykT> Arrayl<T>: : operator - (const Arrayl<T>& v)
assert(n_data==v.n_data);
Arrayio temp(n_data);
'or (int i=0; i<n_data; i++) temp[i] = data[i] - v.data[i];
} return temp;
J дружественные функции
::戶te <class
"e汕1也 operator << (ostreamft s,const Array1<T>& v)
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 Array1<T>& vl, const Arrayl<T>& v2)
if (vl.size() != 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 Arrayl<T>& vl, const Arrayl<T>& v2)
{ return !(vl==v2); }
template <class T> class Array2
private:
// поля данных int rows, cols; Arrayl<T> *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);
Array2<T> Дггау2<Т> дгГау2<Т> дггау2<Т>Array2<T> Дггау2<Т> дгГау2<Т> дггау2<Т>(const (const (const (const(const (const (const (const
Array2<T> Дггау2<Т> дгГау2<Т> дггау2<Т>
Array2<T> Дггау2<Т> дгГау2<Т> дггау2<Т>
(const (const (const (const
(const (const (const (const
operator += operator -= operator + operator -
Аггау2<Т>&);
Аггау2<Т>&);
Аггау2<Т>&);
Аггау2<Т>&);
II функции потокового ввода/вывода
friend ostream & operator << (ostream&, const Array2<T>&); // конец объявления класса Array2
/ инструктор, деструктор и конструктор копирования template <class Т> Array2<T>: :Array2(int г, int с)
.rows(г), cols(c), data2D(new Arrayl<T>[г])
assert(data2D != NULL);
for (int i = 0; i<r; ++i) (data2D+i) -> resize(c);
template <class T> Array2<T>: :Array2(int r, int с, T num) :rows(r) , cols(c) , data2D(n.ew Arrayl<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 Arrayl<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; }
〃 методы
:mpiate <class T> void Array2<T>::resize(int r, int c)
int i;
Arrayl<T> *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];
}
else
for (i=0; i<rows; i++)
{
(data2D+i) -> resize(c);
newArray[i]二 data2D[i];
for (i=rows; i<r; i++) newArrayti].resize(c);
delete [] data2D;
rows = r; cols = c;
data2D = newArray;
template <class T> void Array2<T>::resize(int r, int с, T 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; i<rows; 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> Array 1<T>& Array2<T>: : operator [] (int i) 8口$七
assert(i >= 0 && i < rows);
return data2D[i];
constconst/ te <class T> int Array2<T>::size(int index) 俨皿丄"
const
const
〔sSert(index==0 I I index==l);
:f (index) return cols;
return rows;
}
爲北:爲署寸Lay2<>xrat。-爲北:爲署寸Lay2<>xrat。-(const Array2<T>& m)(const Array2<T>& m)
爲北:爲署寸Lay2<>xrat。-
爲北:爲署寸Lay2<>xrat。-
(const Array2<T>& m)
(const Array2<T>& m)
if (this == &m) return *this;
delete 口 data2D;
CO1S = m.cols; rows = m.rows;
data2D = new Array1<T>[m.rows]; assert(data2D != NULL); for (int i=0; i<m.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 *th.is;
template <class T> Array2<T> Array2<T>: : operator *= (T num) {
for (int i=0; i<rows; i++) data2D[i] *= num;
return *this;
template <class T>
Array2<T> Array2<T>::operator += (const Array2<T>& m) { return *this = *this + m; }
template <class T>
(const Array2<T>& m)(const Array2<T>& m):rray2<T> Array2<T> : : operator 'return *this = *this - m; }
(const Array2<T>& m)
(const Array2<T>& m)
jmplate <class T>
{"ay2<T> Array2<T>::operator + (const Array2<T>& m)
Assert((m.cols == cols) && (m.rows == rows));
Array2<T> temp(rows,cols);
'口工(int i=0; i<rows; i++) temp[i] = data2D[i] + m.data2D[i]; j return temp;
template <class Т>
Array2<T> Array2<T>::operator - (const Array2<T>& m)
assert((m.cols == cols) && (m.rows == rows));
Array2<T> temp(rows,cols);
for (int i=0; Krows; i++) temp[i] = data2D[i] - m.data2D[i]. return temp;'
template <class T>
ostream & operator << (ostream& s,const Array2<T>& m)
for (int i=0; i<m.rows; i++) s << m.data2D[i] << endl; return s;
template <class T>
int operator == (const Array2<T>& ml, const Array2<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 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);
АггауЗ(int, int, int, T);
АггауЗ(const Array3<T> 也);
*Array3();
// методы
void resize(int, int, int);
void resize(int, int, int, T);
дгГау2<Т> & operator 口 (int) const; int size(int = 0) const;
〃 арифметические операторыconst Array3<T>& operator = (const Array3<T>&);const Array3<T>& operator = (T);〃 арифметические операторыconst Array3<T>& operator = (const Array3<T>&);const Array3<T>& operator = (T);Array3<T> operator *=Array3<T> operator +=Array3<T> operator -=Array3<T> operator +Array3<T> operator -Array3<T> operator *=Array3<T> operator +=Array3<T> operator -=Array3<T> operator +Array3<T> operator -(T);(const Array3<T>&);(const Array3<T>&);(const Array3<T>&);(const Array3<T>&);(T);(const Array3<T>&);(const Array3<T>&);(const Array3<T>&);(const Array3<T>&);
〃 арифметические операторы
const Array3<T>& operator = (const Array3<T>&);
const Array3<T>& operator = (T);
〃 арифметические операторы
const Array3<T>& operator = (const Array3<T>&);
const Array3<T>& operator = (T);
Array3<T> operator *=
Array3<T> operator +=
Array3<T> operator -=
Array3<T> operator +
Array3<T> operator -
Array3<T> operator *=
Array3<T> operator +=
Array3<T> operator -=
Array3<T> operator +
Array3<T> operator -
(T);
(const Array3<T>&);
(const Array3<T>&);
(const Array3<T>&);
(const Array3<T>&);
(T);
(const Array3<T>&);
(const Array3<T>&);
(const Array3<T>&);
(const Array3<T>&);
// функции потокового вводы/вывода
friend ostream & operator << (ostreamfe, const Array3<T>&);
// конец описания класса АггауЗ
〃 конструктор, деструктор и конструктор копирования.
template <class Т> АггауЗ<Т>: :АггауЗ(int г, int с, int v)
:rows(г), 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);
template <class T> Array3<T>::АггауЗ(const Array3<T>& m)
• r°ws(m.rows), cols(m.cols), levs(m.levs),
{ data3D(new Array2<T>[m.rows])
assert(data3D != NULL);
} '°r (int i=0; i<m.rows; i++) data3D[i] = m.data3D[i];
/ Методы/ МетодыeaPlate <class T> Array3<T>::~Array3() { delete □ data3D; }
/ Методы
/ Методы
<class T> void Array3<T>::resize(int r, int c, int v)
int i;
Array2<T> *newArray = new Array2<T>[r]; assert(newArray i= if (r <= rows)
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;
rows 二 r; cols = с; levs = v; data3D 二 newArray;
а3личные внутренние операторы
〃 'late <class T> Array2<T> & Array3<T>::operator 口 (int temp 丄 a
'assert(i >= 0 && i < rows);
return data3D[i];
const
template <class T> int Array3<T>::size(int index) const
assert(index>=0 && switch(index)
index<3);
}
}
case 0: return case 1: return default: return
rows; cols;
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; i<m.rows; i++) data3D[i] = m.data3D[i]; return *this;
template <class T> const Array3<T>& Array3<T>::operator = (T
for (int i=0; i<rows; i++) data3D[i] = num; return *this;
num)
;emp工ate <class T> Array3<T> Array3<T>::operator *= (T num)
for (int i=0; i<rows; i++) data3D[i] *= num; return *this;
(const Array3<T>& m)(const Array3<T>& m)(const Array3<T>& m)(const Array3<T>& m)
(const Array3<T>& m)
(const Array3<T>& m)
(const Array3<T>& m)
(const Array3<T>& m)
АггауЗ<Т> АггауЗ<Т>::operator += { return *this = *this + m; }
template <class T>
Array3<T> Array3<T>::operator { 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 == iev Array3<T> temp(rows,cols,levs);
for (int i=0; Krows; 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; Krows; i++) temp[i] = data3D[i] - m.data3D[i]; return temp;
template <class T>
ostream也 operator << (ostreamfe s, const Array3<T>& m)
for (int i=0; Km.rows; i++) s << m.data3D[i] « endl; return s;
template <class T>
int operator == (const Array3<T>& ml, const Array3<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 Array3<T>& ml, const Array3<T>& m2)
{ return !(ml==m2); }
template <class T> class Array4
Rate:
г // поля данных
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;
// арифметичeские операторы const Array4<T>& operator = const Array4<T>& operator =// арифметичeские операторы const Array4<T>& operator = const Array4<T>& operator =(const Array4<T>&); (T);(const Array4<T>&); (T);Array4<T>Array4<T>Array4<T>Array4<T>Array4<T>Array4<T>Array4<T>Array4<T>Array4<T>Array4<T>operator *= operator += operator -= operator + operator -operator *= operator += operator -= operator + operator -(T);(const Array4<T>&);(const Array4<T>&);(const Array4<T>&);(const Array4<T>&);(T);(const Array4<T>&);(const Array4<T>&);(const Array4<T>&);(const Array4<T>&);
// арифметичeские операторы const Array4<T>& operator = const Array4<T>& operator =
// арифметичeские операторы const Array4<T>& operator = const Array4<T>& operator =
(const Array4<T>&); (T);
(const Array4<T>&); (T);
Array4<T>
Array4<T>
Array4<T>
Array4<T>
Array4<T>
Array4<T>
Array4<T>
Array4<T>
Array4<T>
Array4<T>
operator *= operator += operator -= operator + operator -
operator *= operator += operator -= operator + operator -
(T);
(const Array4<T>&);
(const Array4<T>&);
(const Array4<T>&);
(const Array4<T>&);
(T);
(const Array4<T>&);
(const Array4<T>&);
(const Array4<T>&);
(const Array4<T>&);
// функции потокового ввода/вывода
friend ostream & operator << (ostreamfe, const Array4<T>&); // конец объявления класса Array4
копированияr, int c, int v, int b) data4D(new Array3<T>[r])копированияr, int c, int v, int b) data4D(new Array3<T>[r])〃 конструктор, деструктор и конструктор template <class Т> Array4<T>::Array4(int
копирования
r, int c, int v, int b) data4D(new Array3<T>[r])
копирования
r, int c, int v, int b) data4D(new Array3<T>[r])
:rows(r), cols(с), levs(v), blks(b),
assert(data4D != NULL);
for (int i=0; i<r; ++i) (data4D+i) -> resize(c,v,b);
r,int c,int v,int b,T num) data4D(new Array3<T> [r])r,int c,int v,int b,T num) data4D(new Array3<T> [r])Opiate <class T> Array4<T>::Array4(int :rows(r)> cols(c), levs(v), blks(b),
r,int c,int v,int b,T num) data4D(new Array3<T> [r])
r,int c,int v,int b,T num) data4D(new Array3<T> [r])
assert(data4D != NULL);
f°r (int i=0; i<r; ++i) (data4D+i) -> resize(c,v,b,num);
template <class Т> Array4<T>::Array4(const Array4<T>& ш)'
:rows(m.rows) , cols(m.cols), levs(m.levs), blks(m.blks)
data4D(new Array3<T>[m.rows])
assert(data4D NULL);
for (int i=0; Km.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; Krows; 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)
null)null)int i;
null)
null)
Array3<T> *newArray = new Array3<T>[r]; assert(newArray if (r <= rows)
for (i=0; i<r; i++)
〈 (data4D+i) -> resize(c,v5b,value);
newArray[i] = 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];
index<4);index<4);template <class T> int Array4<T>::size(int index) const
index<4);
index<4);
assert(index>=0 && switch(index)
case case casecase case case012012default: }default: }return return return returnreturn return return returnrows; cols; levs; blks;rows; cols; levs; blks;
case case case
case case case
0
1
2
0
1
2
default: }
default: }
return return return return
return return return return
rows; cols; levs; blks;
rows; cols; levs; blks;
"mplate <class T>
:"st Array4<T>& Array4<T>::operator = (const Array4<T>& m) 迂(this == &m) return *this;
delete [] data4D;
cols = m.cols; rows = m.rows; levs = m.levs; blks = m.blks・ data4D = new Array3<T>[m.rows]; assert(data4D != NULL);' for (i=0; i<m.rows; i++) data4D[i] = m.data4D[i];
return *this;
template <class T> const Array4<T>& Array4<T>::operator = (t 口皿) 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; i<rows; i++) data4D[i] *= num; return *this;
template <class T>
(const Array4<T>& m)(const Array4<T>& m)(const Array4<T>& m)(const Array4<T>& m)Array4<T> Array4<T>::operator += { return *this = *this + m; }
(const Array4<T>& m)
(const Array4<T>& m)
(const Array4<T>& m)
(const Array4<T>& m)
template <class T>
Array4<T> Array4<T>::operator -= { return *this = *this - m; }
template <class T>
Array4<T> Array4<T>::operator + (const Array4<T>& m)
assert((m.cols == cols) && (m.rows == rows) 也也
(m.levs == levs) && (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)也也
(m.levs == levs)也也(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;
}
仅mp
jate <class T>
о^
!ащ& operator «
(ostreamfe s, const Array4<T>& m)
i++) s << m.data4D[i] << endl;i++) s << m.data4D[i] << endl;for (int i=0; Km.rows 面皿11 s;
i++) s << m.data4D[i] << endl;
i++) s << m.data4D[i] << endl;
const Array4<T>& m2)0;const Array4<T>& m2)0;const Array4<T>& m2)const Array4<T>& m2)template〈class T>
const Array4<T>& m2)
0;
const Array4<T>& m2)
0;
const Array4<T>& m2)
const Array4<T>& m2)
[t operator == (const Array4<T>& ml,
{ if (ml.size() != m2.size()) return 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, { return ! (ml==m2) ; }
#endif
Класс String
Открытый интерфейс класса String:
String(): конструктор по умолчанию.
String(char): конструктор.
String(int): конструктор.
String (const char*): конструктор.
String (const Stringfe): конструктор копирования.
reverse(): обращение строки.
length(): длина строки .
арифметический оператор:=
оператор взятия индекса:[]
оператор конкатенации: +
оператор преобразования типа: operator char* ()
операторы сравнения: ==, !=, <, <=, >, >=
потоковые операторы: », <<
более детального описания структуры класса и его методов, пожалуйста, °бРЭ1цайтесь к разд. 6.9.
〃 MStrEg.h
工nd“ MSTRING-H
efine MSTRING H
#include <iostream.h>
#include <assert.h>
#include <string.h>
class String
private:
// поля данных int datalength; char *data;
public:
// конструкторы и деструктор
String();
String(char);
String(int);
String(const char *);
String(const Stringft);
"String。;
// оператор присваивания
const Stringft operator = (const Stringft);
// методы и оператор индекса
String reverseO const;
int length() const;
char& operator [](int) const;
// оператор преобразования
operator const char *() const;
// конкатенация
friend String operator + (const Stringfe, const Stringft);
// дружественные операторы
friend ostream& operator « (ostreamfe, const Stringfe);
friend istreamfe operator » (istream&, String^);
};
friend
int operator <
(const String&, const Stringfe);
friend
int operator <=
(const Stringfe, const Stringfe);
friend
int operator !=
(const Stringft, const String&);
friend
int operator ==
(const String&j const String&);
friend
int operator >=
(const Stringfe, const Stringft);
friend
int operator >
(const String&, const Stringfe);
// операторы отношения
класса
.вализация класса
〃 Ро11СТруктор и деструкторы
// К -.:String() : datalength(l), data(new char[1]) gtflB&* ■
a^ert (data != NULL); data]。〕= W;
}
String: :String(char c): 'assert(data != NULL); data[0] = c;
data[l]Н ’\0';
datalength(2) , data(new char ⑵)
String::String(int size) {
assert(size >= 0);
datalength = size + 1;
data = new chcir [datalength]; assert(data != NULL);
data[0] = J\0J; }
// строка определяется как строка NULL
String: : String (const char *s)
:datalength (strlen(s) + 1) , data (new chcir [datalength])
assert(data != NULL); strcpy(data,s);
^ring::String(const String &s)
:datalength ( strlen(s. data) + 1) , data (new chcir [datalength])
assert(data != NULL); strcpy(data,s.data);
尸啥:~string()delete [] data; }
// err
ииаранор присваивания
°“st Stringfe String::operator = (const String& s)
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); }
char& String::operator [](int index) const
{ return data[index]; }
// оператор преобразования
String::operator const char *() const
{ return data; }
// друже с тв енные операторы
ostream& operator << (ostreamfe out, const String& s)
out << s.data;
return out;
}
(istreamfe in, String& str)(istreamfe in, String& str)qai成 operator »
(istreamfe in, String& str)
(istreamfe in, String& str)
(char temp [1000]; 好(in » temp) str = temp;
else str = return in;
力 оператор конкатенации
Stringft s2)Stringft s2)jring operator + (const Stringfe si, const
Stringft s2)
Stringft s2)
String S(sl.length() + s2.1ength()); strcpy(S.data, si.data);
strcat(S.data, s2.data);
return S;
I/ операторы отношения
int operator < (const Stringfe left, const Stringfe right) { return strcmpdeft.data,right .data) < 0; }
int operator <= (const Stringfe left, const Stringfe right)
{ return strcmpdeft. data,right .data) <= 0; }
int operator == (const Stringfe left, const String& right) { return strcmpdeft. data,right .data) == 0; }
int operator != (const Stringfe 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 3 const String& right) { return strcmp(left .data,right .data) >= 0; }
#endif
10・10・ Класс BitVec
Открыты访 интерфейсОткрыты访 интерфейскласса BitVec:класса BitVec:
Открыты访 интерфейс
Открыты访 интерфейс
класса BitVec:
класса BitVec:
BitVec(): конструктор по умолчанию.
BitVec(unsigned int): конструктор.
BitVec(unsigned int, unsigned int): конструктор.
BitVec(const BitVecfe): конструктор копирования.
*iNe(): размер битового вектора.
reset (unsigned int) : переопределение размера битового вектора
恥B网恥B网reset (unsigned int, unsigned int): переопределение размера 61r вектора и инициализация битовых полей.'
恥B网
恥B网
set (unsigned int): включение отдельного бита.
clear (unsigned int): выключение отдельного бита.
test (unsigned int): проверка отдельного бита на включение.
f lip(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"
// вектор двоичных величин (0 или 1)
class BitVector
private:
unsigned int bsize;
Vector<unsigned 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 BitVector&);
unsigned int size() const; // размер битового вектора void reset(unsigned int);// переопределение размера
gid reset(unsigned int, unsigned int);
// переопределение размера битового вектора
// и инициализация битовых полей
〃 битовые операции
void set(unsigned int);
void clear(unsigned int);
jut test(unsigned int) const;
void flip(unsigned int);
〃 операции над наборами
BitVector unionSet(const BitVector&);
BitVector intersectSet(const BitVector&);
BitVector differenceSet(const BitVector&);
int operator == (const BitVector&);
int subset(const BitVector&);
// Друзья
friend ostream& operator « (ostream&, const BitVectorfe);
};
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);
elsedata.reset((num+7)/8);
BitVector::BitVector(const BitVector& b): bsize(b.bsize)3 data(b.data) {}
〃 возвращается размер битового вектора
^signed int BitVector: :size() const { return bsize; }
// Переопределяется размер битового вектора :Qid BitVector::reset(unsigned int num) '^size = num; data.reset((num+7)/8, 0); }
/ Переопределяется размер битового вектора
и инициализация оставшихся битов значениями (0 или 1) “Qid BitVector::reset(unsigned int num, unsigned int value)
bsize = num;
if (value) data.reset((num+7)/8, OxFF);
elsedata.reset((num+7)/8, 0);
// установка определенного бита в векторе
void BitVector::set(unsigned int index)
{ data[ЪуteNumber(index)] 1= 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
// вычисление сдвигом всей величины проверкой
// младших 3 битов индекса
const int shiftAmount = index & 07;
// строим маску сдвигом величины ,1,
// влево на заданную величину
return 1 « shiftAmount;
}
// формирование объединения
BitVector BitVector::unionSet(const BitVector& Ъ)
assert(bsize == b.bsize);
BitVector result(bsize);
int i, total = (bsize+7)/8;
for (i=0; Ktotal; i++)
result.data[i] = data[i] I b.data[i];
return result;
}/ формирование пересечения
g-tVector BitVector::intersectSet(const BitVector& 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 BitVector& 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:
}
〃 возврадцает истину, если набор входит в аргумент
;11t BitVector: : subset (const BitVector& 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;
// вывод битового вектора в поток
ostreamfe operator << (ostream& s, const BitVector& b)
for (int i=0; Kb.bsize; i++) s << b.test(i); return s;
#endif
Класс MList
Открытый интерфейс класса List:
List(): конструктор по умолчанию.
List (const List<T>&): конструктор копирования.
add(T): добавление элемента в начало списка.
duplicate(): создание дубликата списка.
f irst_Node(): первый элемент списка.
is_Empty(): проверка списка на пустоту.
deleteAllNodes(): очистка списка.
is_Include(T v): проверка на наличие v в списке.
delete一First (): удаление первого элемента списка.
Открытый интерфейс класса Listiterator:
Listiterator (List<T>&): конструктор.
init(): инициализация итератора.
de]_ete_Current (): удаление элемента, отмеченного итератором.
add.Bef ore (const Т v): добавление элемента v до элемента, отмеченного итератором.
add_Af ter ( const Т 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; // проверка наличия текущего злем©
возвращает текущий элемент оператор инкремента оператор присваиваниявозвращает текущий элемент оператор инкремента оператор присваивания#ifndef MLIST-H;define ML工ST_Н#ifndef MLIST-H;define ML工ST_Н#include <assert.h>#include <assert.h>// опережающее объявлениеtemplate<classT>classLink;template<classT>classListiterator;template<classT>classListprotected:// поля данныхLink<T> *head; // голова указывает на первый элемент// опережающее объявлениеtemplate<classT>classLink;template<classT>classListiterator;template<classT>classListprotected:// поля данныхLink<T> *head; // голова указывает на первый элементfriend class ListIterator<T>; public:// конструкторыList ();List(const List<T>&);virtual ~List();friend class ListIterator<T>; public:// конструкторыList ();List(const List<T>&);virtual ~List();// методы// методыvoidadd(const T);List<T> * duplicate() const;Tfirst.Node() const;intis_Empty() const;voidadd(const T);List<T> * duplicate() const;Tfirst.Node() const;intis_Empty() const;// виртуальные функции// виртуальные функцииvirtual virtual virtual virtual};virtual virtual virtual virtual};void int void constvoid int void constdeleteAHNodes ();is_Include(T) const;delete_First();List<T>& operator 二(const List<T>&);deleteAHNodes ();is_Include(T) const;delete_First();List<T>& operator 二(const List<T>&);tual T operator ()0 = 0; // "tual int operator ++() = 0; // “ rnal void operator = (const T) = 0;//
возвращает текущий элемент оператор инкремента оператор присваивания
возвращает текущий элемент оператор инкремента оператор присваивания
#ifndef MLIST-H
;define ML工ST_Н
#ifndef MLIST-H
;define ML工ST_Н
#include <assert.h>
#include <assert.h>
// опережающее объявление
template<classT>classLink;
template<classT>classListiterator;
template<classT>classList
protected:
// поля данных
Link<T> *head; // голова указывает на первый элемент
// опережающее объявление
template<classT>classLink;
template<classT>classListiterator;
template<classT>classList
protected:
// поля данных
Link<T> *head; // голова указывает на первый элемент
friend class ListIterator<T>; public:
// конструкторы
List ();
List(const List<T>&);
virtual ~List();
friend class ListIterator<T>; public:
// конструкторы
List ();
List(const List<T>&);
virtual ~List();
// методы
// методы
voidadd(const T);
List<T> * duplicate() const;
Tfirst.Node() const;
intis_Empty() const;
voidadd(const T);
List<T> * duplicate() const;
Tfirst.Node() const;
intis_Empty() const;
// виртуальные функции
// виртуальные функции
virtual virtual virtual virtual
};
virtual virtual virtual virtual
};
void int void const
void int void const
deleteAHNodes ();
is_Include(T) const;
delete_First();
List<T>& operator 二(const List<T>&);
deleteAHNodes ();
is_Include(T) const;
delete_First();
List<T>& operator 二(const List<T>&);
hmpiathmpiate <class T> class Link
hmpiat
hmpiat
Private:
// конструкторы
Link(const T, Link<T> *);
Link(const Link<T>&);
Link<T> * duplicate() const;
// поля данных
Tdata;
Link<T> *next;
friend class List<T>;
friend class ListIterator<T>;
public:
Link<T> * insert(const T);// вставка нового элемента ]
// текущей величины
};
template <class Т> class Listiterator : public Iterator<T>
protected:
// поля данных
Link<T> *current;
Link<T> *previous;
List<T>& list;
public:
// конструкторы
ListIterator(List<T>&);
ListIterator(const ListIterator<T>&);
// протокол итератора
virtual int init();
virtual T operator ()();
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()
{ deleteAHNodes(); }
бгевление новой величины в начало связанного списка //<class Т> void List<T>: :add(const Т val)
(head = new Link<T>(val, head);
a^ert(head != NULL);
даЛение всех элементов из списка
"mplate <class Т> void List<T>: :deleteAHNodes()
Link<T> *nxt; nxt n NULL;
〃 удаление элемента, указываемого p 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 (1st.is_Empty()) head = NULL;
else
Link<T> *firstLink = 1st.head;
head = firstLink->duplicate();
"mplate <class T>
°nst List<T> & List<T>::operator = (const List<T>& 1st)
if (this != &lst)
// дублирование элементов списка 1st
if (1st.is_Empty()) head = NULL;
else
Link<T> *firstLink 工 1st.head; head = firstLink->duplicate();
return *this;
// возвращаем первый элемент списка
template <class T> T List<T>::first_Node() const
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 Т> void List<T>:idelete^First()
assert(head != NULL);
Link<T> *p = head;
head = p->next;
delete p;
// реализация класса Link
// вставка нового элемента позади текущего
template <class Т> Link<T> * Link<T>::insert(const T va
next = new Link<T>(val, next);
那5虹七(next != NULL);
Return next;
да11ие и инихщализация нового связанного поля
"';ate <class Т> Link<T>::Link(const T val, Link<T> *nxt) 嘰 data(val), next(nxt) {}
〃 конструктор копирования
〃血plate <class T> Link<T>::Link(const Link<T> &lst)
.data(lst.data), next(1st.next) {}
〃 дублирование связи
template <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;
}
// реализация класса Listiterator
// создание и инициализация нового списка
template <class Т> ListIterator<T>::Listiterator(List<T>& aLis :list(aList)
{ EM(); }
〃 конструктор копирования
template <class T>
^^■stIterator<T> : : List iterator (const ListIterator<T>& x)
:list(x.list) { init(); }
;/ Установка итератора на первый элемент списка
;mp]_ate <class Т> int ListIterator<T>:: init()
Previous = NULL; current = list.head;
} return current != NULL;
// возвращаем величину текущего элемента
template <class Т> Т ListIterator<T>::operator ()()
assert(current != NULL);
return current->dat a;
// проверка закрытия итератора
template <class T> int ListIterator<T>::operator !()
// если текущая связь указывает на удаленное значение, // переводим её на следующую позицию
if (current == NULL)
if (previous != NULL) current = previous->next;
// проверка на корректность
return current != NULL;
// оператор инкремента, который переводит указатель
//на следующий элемент
template <class Т> int ListIterator<T>::operator ++()
// если текущая связь удалена
if (current == NULL)
if (previous == NULL) current = list .head.;
elsecurrent = previous->next;
else // продвижение указателя
previous = current;
current = current->next;
// возвращаем истину, если текущий элемент корректен return current != NULL;
// оператор присваивание : модифицируем значение
// текущего элемента
template <class Т> void ListIterator<T>::operator = (const
assert(current != NULL);
current->data = val;
аЛЯем текущий элемент из списка
// 'ate <class Т> void ListIterator<T>: :delete_Current() temp 丄
'aSsert(current != NULL);
// удаляем первый элемент
if (previous == NULL) list.head = current->next; else previous->next = current->next;
// удаляем текущий элемент и установка указателя в нуль delete current;
current 二 NULL;
}
// добавляем новый элемент в список перед текущим значением template <class Т> void ListIterator<T>::add_Before(const T val)
if (previous)// находимся не в начале
previous = previous->insert(val);
else// начало списка
{
list.add(val);
previous = list.head;
current 工 previous->next;
// добс1вляем новый элемент в список после текущего значения template <class Т> void ListIterator<T>: : add_Af ter (const T val) {
if (current != NULL)// находимся не в начале
current->insert(val);
else if (previous != NULL)// конец списка
current = previous->insert(val);
else// начало списка
} list.add(val);
#endif
1°・12・ Класс Polynomial
^тКрытый интерфейс класса Polyterm:
^olytermCchcir*): конструктор, дающий имя переменной (по умолчанию "х").
PolytermCconst Polyterm<T>&): конструктор копирования.
^о1уГегт<Т>& operator+=(Polyterm<T>)
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>)团
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.
"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 (Polyt erm<T>): интегрирование терма с точностью до константы интегрирования.
ostreamft operator<<(ostreamft, 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>)
Polynomial<T> operator+(Polyterm<T>)
Polynomial<T> operator-(Polyterm<T>)
Polynomial<T> operator+=(T)
Polynomial<T> operator-=(T)
Polynomial<T> operator+(Polynomial<T>)
Polynomial<T> operator-(Polynomial<T>)
operator+=(Polynomial<T>)
poiynomial<T>& operator-=(Polynomial<T>) polynomial<T>& operator=(Polyterm<T>)
* polynomial<T> operator+(T)
* polynomial<T> operator-(T)
* polynomial<T>& operator=(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> operator*(Polynomial<T>)
.polynomial<T> operator"(unsigned int)
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> operator/(Polynomial<T>): деление с игнорированием остатка.
Polynomial<T>& operator%=(T)
Polynomial<T>& operator^=(Polyterm<T>): остаток после деления.
Polynomial<T>& operator%= (Polynomial<T>): остаток после деления.
Polynomial<T> operatorX(T)
Polynomial<T> operatorX(Polyterm<T>): остаток после деления.
Polynomial<T> operatorX(Polynomial<T>): остаток после деления.
int operator==(Polynomial<T>)
int operator==(Polyterm<T>)
int operator==(T)
int operator !=(Polynomial<T>)
int operator !=(Polyterm<T>)
int operator !=(T)
T value (T t): вычисление значения многочлена в точке t.
Т operator() (Т t): вычисление значения многочлена в точке t.
"Polynomial(): деструктор.
класса Polynomial:
Polynomial<T> operator-(Polynomial<Т>)
Polynomial<T> operator+(Polynomial<T>)
Polynomial<T> operator+(Polyterm<T>,Polynomial<T>)
Polynomial<T> operator-(Polyterm<T>,Polynomial<T>)
Ро1упот1аКТ> Diff (Polynomial<T>): дифференцирование многочлена.
Polynomial<T> Int (Polynomial<T>): интегрирование многочлена с стью до константы.‘°口眩
Polynomial<T>Polynomial<T>Polynomial<T> многочлена.Polynomial<T>Polynomial<T>Polynomial<T> многочлена.operator+(T,Polynomial<T>) operator-(T,Polynomial<T>)
Polynomial<T>
Polynomial<T>
Polynomial<T> многочлена.
Polynomial<T>
Polynomial<T>
Polynomial<T> многочлена.
STepM^STepM^operator+(Polyterm<T>,Polyterm<T>): сумма
STepM^
STepM^
разность полИТер.разность полИТер.Polynomial<T> operator-(Polyterm<T>,Polyterm<T>): ма и многочлена.
разность полИТер.
разность полИТер.
Polynomial<T> operator+(T,Polyterm<T>)
Polynomial<T> operator-(T,Polyterm<T>)
Polynomial<T> operator+ (Polyterm<T>, T): сумма константы и политерм как многочлен.
Polynomial<T> operator- (Polyterm<T> ,Т): разность константы и полите ма как многочлен.
Polynomial<T> operator*(T,Polynomial<T>)
Polynomial<T> operator*(Polyterm<T>,Polynomial<T>)
int operat or==(Polyt erm<T>,Polynomial<T>)
int operator==(T,Polynomial<T>)
int operator !=(Polyterm<T>,Polynomial<T>)
int operator !=(T,Polynomial<T>)
ostreamft operator<<(ostreani&,Polynomial<T>&)
Для более детального описания структуры класса и его методов, пожалуйста, обращайтесь к разд. 6.12.
〃 poly.h
#ifndef „POLYNOMIAL
#define .POLYNOMIAL
#include 〈string.h>
#include <iostream.h>
// предположение : T имеет подобранный тип // и может вести себя как числовой тип
template <class Р>
Р 一poly—power(Р x,unsigned int у)
{
P result(1);
for(int i=l;i<=y;i++) result*=x;
return result;
// класс Polyterm для мономов
public:
f polyterm(char *sym=”x“);
polyterm(const Polyterm<T> &);
polyterm<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 operat or==(const Polyterm<T>&) const;
int operator!=(const Polyterm<T>&) const;
polyterm<T>& operat or=(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>& ~Polyterm();
protected:
Polyterm(T coeff,char *sym,unsigned int exp);
char *symbol;
T coefficient;
unsigned int exponent;
};
template <class T>
P°lyterm<T>: : Polyt erm (char *sym) : coefficient (T(l))
exponent=l;
symbol=new chcir [strlen(sym)+l];
cpy (symbol, sym);
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)&&
(!strcmp(symbol,p.symbol)||(exponent==0)))
coeff iclent +=p.coefficient;
return *this;
template <class T>
Polyterm<T>& Polyterm<T>::operator-=(const Polyterm<T> &p)
if((p.exponent==exponent)&&
(!strcmp(symbol,p.symbol)I I(exponent==0)))
coefficient-=p.coefficient;
return *this;
template <class T>
Polyterm<T>& Polyterm<T>::operator*=(const Polyterm<T> &p)
char *sym;
if(!strcmp(symbol,p.symbol)I I(exponent==0)|I(p.exponent==0))
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;
te 〈class T>
r yterm<T>^ Polyterm<T>::operator/=(const Polyterm<T> &x)
^.^x. coeff icient==T (0))
{#tbis=T(0);
用皿n *this;
} efficient/=x.coefficient;
if (exponent>=x.exponent) exponent-=x.exponent;
;i$e coefficient=T(0);
return *this;
template <class T> Polyterm<T>& Polyterm<T>::operator/=(T c)
if (c==T(0))
{
*this~T(0);
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;
:mplatG <class T> Polyterm<T> Polyterm<T>::operator/(T c) const p°lyterm<T> xl(*this);
xl/=c;
Jeturn xl;
template <class T>
产 Polyterm<T>::operator>(const Polyterm<T> &p) const
Jeturn (exponent>p.exponent);
template <class Т>
int Polytenn<T>::operator==(const Polyterm<T> &p) const
if((coefficient==T(0))&&(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)
exponent=0;
coefficient=c;
return *this;
template <class T>
int Polyterm<T>::equal(const Polyterm<T> &p) const
return ((!strcmp(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(0),symbol,0);
Polyterm<T>(coefficient*p.coefficient,symbol,exponent+p.exponent);
<class T> Polyterm<T> operator*(T c,const Polyterm<T> &p
template <class T>
1yterm<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(0)*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+1);
P • ©xponent++;
Return p;
template <class Т> Т Polyterm<T>: : valued х) {
return coefficient*__poly__power(x,exponent);
}
template <class T>
ostream &operator«(ostream &o,const Polyterm<T> &p)
if(((p.coefficient!=T(l))&&(p.coefficient!=T(-1)))I|
(p.exponent==0))
o<<"(H<<p* coefficient<<H)";
if(p.exponent) o<<p.symbol;
if (p.exponent>l) о«"Л,,<<р.exponent;
return o;
template <class T> Polyterm<T>::"PolytermO { deleted symbol; }
template <class T>
Polyterm<T>::Polyterm(T coeff,char *sym,unsigned int exp) :coefficient(coeff)
exponent=exp;
symbol=new cheir [strlen(sym)+l];
strcpy(symbol,sym);
}
//структура вспомогательного списка многочлена
template <class T> struct polyListitem {
Polyterm<T> *item;
polyListItem<T> *next;
polyListItem<T> *previous;
};
// класс Polynomial
template <class T>
class Polynomial {
public:
Polynomial(Polyterm<T> x) : variable(x.variable()), head(0) О;
Polynomial(char *sym): variable(Polyterm<T>(sym))> head(0) {};
Polynomial(const Polynomial<T>&);
Polynomial(int);
Polynomial<T>& operator=(const Polynomial<T>&);
friend Polynomial<T> operator+(const Polynomial<T>&); friend Polynomial<T> operator-(const Polynomial<T>&); polynomial<T>& operator+=(const Polyterm<T>&); polynomial<T>& operator-=(const Polyterm<T>&); polynOmial<T> operator+(const Polyterm<T>&) const; polyn°mial<T> operator-(const Polyterm<T>&) const; polynomial<T> operator+=(T);
polynomial<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>&); Polynomial<T>& 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);
olynomial<T>& operator/=(const Polyterm<T>&); Polynomial<T>& operator/=(const Polynomial<T>&);
pQlynomial<T> operator/(T) const;
Polynomial<T> operator/(const Polytenn<T>&) const;
Polynomial<T> operator/(const Polynomial<T>&) const;
Polynomial<T>& operatorX=(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=s(const Polyterm<T>&) const;
int operator»(const Polynomial<T>&) const;
friend int operator==(T,const Polynomial<T>&);
friend int operator==(const Polytenn<T>&,const Polynomial<T>&) *
int operator!=(T) const;
int operator!=(const Polytenn<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()(T);
friend ostream &operator<<(ostreamfe,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;
polyList!tem<T> *templ=p.head,*temp2;
variable=p,variable;
if(tempi)
head=new polyListItem<T>;
head->item=new Polyterm<T>(*(templ->item));
head->previous=NULL;
head->next=NULL;
temp 1s t emp 1 - >nex t;
}
temp2=head;
while(tempi)
temp2->next=new polyList!tem<T>;
(temp2->next)->previous=temp2;
聞p2=temp2->next;
tещр2->item=new Polyterm<T>(*(templ->item));
:emp2->next=NULL;
>empl=temP1">next;
opiate <class T> Polynomial<T>::Polynomial(int c)
[f (c!=0)
^variable=Polyterm<T>(hx");
head=new polyListItem<T>;
head->item=new Polyterm<T>(T(c)*variable*0);
head->previous=NULL;
head->next=NULL;
}
else
variable=Polyterm<T>(hx");
head=NULL;
}
template <class T>
Polynomial<T>& Polynomial<T>: : operator=(const Polynomial<T> &p) {
if (head==p.head) return *this;
// уничтожение многочлена
polyListItem<T> *temp=head;
Va^iable=p.variable;
while (temp)
{
head=t emp->next;
delete temp->item;
delete temp;
}tGmp=head;
〃 создание нового многочлена
PolyListItem<T> *templ=p.head,*temp2;
Variable=p.variable;
if (tempi)
head=new polyListItem<T>;
head->item=new Polyterm<T>(*(templ->item));
temp2=new polyListItem<T>;
temp2->next=temp;
temp2->previous=temp->previous;
式(tGmp->previous==NULL) head=temp2;
else (temp->previous)->next=temp2;
temp->previous=temp2;
teffip2->item=new Polyterm<T>(x);
glse if(*(temp->item)>x)
temp2=new polyListItem<T>;
temp->next=temp2;
temp2->next=NULL;
temp2->previous=temp;
temp2->item=new Polyterm<T>(x);
}
}
tidy。;
return *this;
template <class T>
Polynomial<T>& Polynomial<T>::operator-=(const Polyterm<T> &x)
*this+=-x;
return *this;
template <class T> Polynomial<T>
Polynomial<T>::operator+(const Polyterm<T> &x) const
if(head==NULL) return (Polynomial<T>(x)+=x);
^return (Polynomial<T>(*this)+=x);
:emplane <class T> Polynomial<T>
^°lynomial<T>::operator-(const Polyterm<T> &x) const if (head==NULL) return (Polynomial<T>(x)-=x);
「turn (Polynomial<T>(*this)-=x);
.opiate <class T> Polynomial<T> Polynomial<T>::operator+=(T x) P°lyterm<T> y;
*this+=y;
return *this;
template <class T> Polynomial<T> Polynomial<T>::operator-: {
Polyterm<T> у;
У=х;
*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;
Polynomial<T> pl(*this);
polyListItem<T> *temp=p.head;
while(temp) {
pl+=*(temp->item);
t emp=t emp->next;
return pl;
template <class T> Polynomial<T>
Polynomial<T>::operator-(const Polynomial<T> &p) const {
if(head==NULL) return T(-l)*p;
Polynomial<T> pl(*this);
polyListItem<T> *temp=p.head;
while(temp) {
pl-=*(temp->item);
t emp=t emp->next;
}
return, pl;
jate <class T> Polynomial<T>&
修“? m'ial<T>: : operator+= (const Polynomial<T> &p) 卩":1ГП (*this=*this+p); }
1ate <class T> Polynomial<T>&
”P ornial<T>: :operator-=(const Polynomial<T> &p)
?R;umR (*this=*this-p) ; }
MPMPlate <class T> Polynomial<T>&late <class T> Polynomial<T>&PM,PM,omial<T>::operator=(const Polyterm<T> &x)omial<T>::operator=(const Polyterm<T> &x){ return{ return*this=Polynomial<T>(variable)+x; }*this=Polynomial<T>(variable)+x; }
MP
MP
late <class T> Polynomial<T>&
late <class T> Polynomial<T>&
PM,
PM,
omial<T>::operator=(const Polyterm<T> &x)
omial<T>::operator=(const Polyterm<T> &x)
{ return
{ return
*this=Polynomial<T>(variable)+x; }
*this=Polynomial<T>(variable)+x; }
template〈class T>
PolynomiaKT> Dif f (Polynomial<T> p)
Polynomia.KT> pl (p); polyListItem<T> *temp=pl.head; while (temp)
*(temp~>item)=Diff(*(temp->item)); temp=temp->next;
}
pl.tidyO ;
return pl;
template <class T> Polynomial<T> Int(Polynomial<T> p)
Polynomial<T> pl(p);
polyListItem<T> *temp=pl.head; while (temp)
*(temp'->item) =Int (* (temp->item));
}temp=te1np->next;
fmplate returnfmplate returnate umate umPLtidy(); retnm pl ;
fmplate return
fmplate return
ate um
ate um
<class T> Polynomial<T> Polynomial<T>::operator+(T c) const (*this) -s- (c* (variable'O) ) ; }
<class T> Polynomial<T> Polynomial<T>::operator-(T c) const (^this)-(c*(variable"0)); }
“mplat® <cl3ss T>
Polynomial<T> operator+(T с,const Polynomial<T> &р) { 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> pl(xl.variable());
return pl+xl+x2;
}
template <class T> Polynomial<T>
operator-(const Polyterm<T> &xl, const Polyterm<T> &x2) {
Polynomial<T> pl(xl.variable());
return pl+xl-x2;
}
template <class T> Polynomial<T>
operator+(T xl,const Polyterm<T> &x2)
{
Polynomial<T> pl(x2.variableO);
return pl+xl+x2;
}
template <class T> Polynomial<T>
operator-(T xl,const Polyterm<T> &x2)
{
Polynomial<T> pl(x2. variableO);
return pl+xl-x2;
}
template <class T> Polynomial<T>
operator+(const Polyterm<T> fcxl,T x2)
Polynomial<T> pl(xl.variableO);
return (pl.operator+(xl)).operator+(x2);
r-.tor- (const Polyterm<T> &xl ,T x2)
”lym
,omial<T> pl (xl. variable ());
田UE
(pl.operator+(xl)).operator-(x2);
<class T> Polynomial<T>&
template
polynomial<T>::operator*=(T c)
polyterm<T> x;
#c;
return *this*=x;
}
template <class T> Polynomial<T>&
p0lynomial<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> pl(th.is->variable),p2(this->variable);
polyListItem<T> *temp=p.head;
while(temp)
pl=*this;
pl*=*(temp->item);
P2+=pl;
^'temp=temp~>iiext ;
*this=p2;
/8口1口 *this;
,mplat® <class T》Polynomial<T> ^°^ynomia3xT>::operator*(T c) const
Polynomial<T> pl(*this);
pl*=c;
return pl;
}
template <class T> Polynomial<T>
Polynomial<T>::operator*(const Polyterm<T> &x) const
Polynomial<T> pl(*this);
pl*=x; return pl;
template <class T> Polynomial<T>
Polynomial<T>::operator*(const Polynomial<T> &p) const
Polynomial<T> pl(*this);
pl*=p; return pl;
}
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;
tidy();
return *this;
^nonls al<T> : : operator/=(const Polyterm<T> &x)
{ , yjistItem<T> *temp=head;
加。(temp)
{*((emp_>item)/=x;
temp-temP_>next;
田urn *this;
template <class T> Polynomial<T>&
polynonual<T>::operator/=(const Polynomial<T> &p) {
polyListItem<T> *templ=head,*temp2=p.head;
polynomial<T> result(this->variable);
Polyterm<T> temp(this->variable),zero;
zero=T (0);
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> Polynomial<T>
^°lynomial<T>::operator/(T c) const
Polynomial<T> pl(*this);
PV=c;
Jetum pl ;
;e$plate <class T> Polynomial<T>
^yriomial<T> : : operator/(const Polyterm<T> &x) cons
Polynomial<T> pl(*this);
Р1/二x;
Jetum pi,
template <class Т> Polynomial<T>
Polynomial<T>::operator/(const Polynomial<T> &p) const
Polynomial<T> pl(*this);
pi/=P;
return pl;
template <class T> Polynomial<T>& Polynomial<T>::operator^(T
{ return *this-=c*(*this/c); }
template <class T> Polynomial<T>&
Polynomial<T>::operatorX=(const Polyterm<T> &x)
{ return *this-=x*(*this/x); }
template <class T> Polynomial<T>&
Polynomial<T>::operatorX=(const Polynomial<T> &p)
{ return *this-=p*(*this/p); }
template <class T> Polynomial<T>
Polynomial<T>::operatorX(T c) const
{ return *this-c*(*this/c); }
template <class T> Polynomial<T>
Polynomial<T>: : operator/i(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;
即P®e { return即P®e { return<class T><class T>{ return{ return
即P®e { return
即P®e { return
<class T>
<class T>
{ return
{ return
late <class T>
constconstt6 ' polyBoniial<T> : : operator== (const Polynomial<T> &p) Wcturii ( (*this-p) .head==NULL) ; }
const
const
<class T> int operator==(T c,const Polynomial<T> &p) p==c; }
template
p==x; }p==x; }怔 operator==(const Polyterm<T> &x,const Polynomial<T> &p)
p==x; }
p==x; }
template <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>::value(T c) {
polyListItem<T> *temp=head;
T sum二T(0); while(temp) {
smn+=(temp->item)->value(c); temp=temp->next;
r®turn sum;
template <class T> T Polynomial<T>::operator()(T c)
'return value(c); }
template <class T>
"stream &operator<<(ostream &o,const Polynomial<T> &p)
polyListItem<T> *temp=p.head;
Т zero(0);
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=t emp->next;
delete temp->item;
delete temp;
temp=head;
template <class T> void Polynomial<T>::tidy(void)
polyListItem<T> *temp,*temp2;
while ((head!=NULL)&&((T(0)*(*(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(0)*(*(temp->item)))==(*(temp->item))))
if(temp->previous) (temp->previous)->next=temp->next;
if(temp->next) (temp->next)->previous=temp->previous;
delete temp->item;
jelete temp;
#endif#endif
#endif
#endif
Класс Set
Открытый интерфейс класса Set:
Set О: конструктор по умолчанию.
Set (const T): конструктор для простого элемента.
wSet(); деструктор.
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&; sSet<T>&): оператор вывода в поток.
Для более детального описания структуры класса и его методов, пожалуйста, обращайтесь к разд. 6.13.
/ / set .h.
#ifndef _SET
define _SET
^include <iostream>
// для exit(0)// для exit(0)^include <stdlib.h>
// для exit(0)
// для exit(0)
Using namespace std;
Public:Set();Set(const T);~Set();Public:Set();Set(const T);~Set();// объявление класса Set template <class T> class Set
Public:
Set();
Set(const T);
~Set();
Public:
Set();
Set(const T);
~Set();
// конструктор по умолчанию
// конструктор простого элемента
// деструктор
// конструктор копировав // оператор присваивания// конструктор копировав // оператор присваиванияSet(const Set <Т>&);
// конструктор копировав // оператор присваивания
// конструктор копировав // оператор присваивания
Set<T>& operator = (const Set<T>&);
int cardinalityO const; // число элементов
friend Set<T> operator + (Set<T>&,Set<T>&); // объединение friend Set<T> operator * (Set<T>&,Set<T>&); // пересечение friend int operator == (Set<T>&,Set<T>&);
private:void first_element() // void next_element(); // T current_element() const;// int is_at_end() const; // void add_at_end(const T); // void release(); //private:void first_element() // void next_element(); // T current_element() const;// int is_at_end() const; // void add_at_end(const T); // void release(); //повторная установка, первого элемента повторный переход к следующему повторный возврат текущего элемента повторно идем в конец добавление к списку с конца очистка памятиповторная установка, первого элемента повторный переход к следующему повторный возврат текущего элемента повторно идем в конец добавление к списку с конца очистка памяти
private:
void first_element() // void next_element(); // T current_element() const;// int is_at_end() const; // void add_at_end(const T); // void release(); //
private:
void first_element() // void next_element(); // T current_element() const;// int is_at_end() const; // void add_at_end(const T); // void release(); //
повторная установка, первого элемента повторный переход к следующему повторный возврат текущего элемента повторно идем в конец добавление к списку с конца очистка памяти
повторная установка, первого элемента повторный переход к следующему повторный возврат текущего элемента повторно идем в конец добавление к списку с конца очистка памяти
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;
//
ссылка на этот элемент
void fail(const char[]) const; // отказ от множества
Set_DESC* the_desc;Box* the_current;Set_DESC* the_desc;Box* the_current;// описание множества
Set_DESC* the_desc;
Box* the_current;
Set_DESC* the_desc;
Box* the_current;
// указатель на текущий элемент
// определение класса Set template <class Т> Set<T>::Set()
the_desc = new Set_DESC[l];
if(the.desc == (Set.DESC*) MULL)
fail("He могу открыть дескриптор множества");
the_desc -> no_of„elements = 0;
the_desc -> references = 1;
the.desc -> root = (Box*) NULL;
the_desc -> last = (Box*) NULL;
opiate <class T> Set<T>::Set(const T
item)
the_desc = new Set_DESC[l];
if(the_desc == (Set_DESC*) NULL) fail("He могу открыть дескриптор
множества");
the_desc -> the_desc -> the„desc -> if(the_desc fail("He
no_of.elements 二 1;
references = 1;
root = new Box [1];
-> root == (Box*) NULL)
могу открыть дескриптор множества");
the_desc -> last = the_desc -> root;
the_desc -> root->value = item;
the__desc -> root->next = (Box*) NULL;
template <class T> void Set<T>::release()
if (— (th.e_desc->ref erences)==0) {
Box* p_active = th.e_desc->root; while(p_active != (Box*) NULL) {
Box* p_remove = p_active; p_active = p_active->next; delete [] p_remove;
}
delete [] th.e_desc;
}
template <class T> Set<T>::~Set() { release (); }
;emplate <class T> void Set<T>::fail(const char mes[]) const
cout << "\n" << "Ошибка класса : Set" << "\пи;
cout << mes << "\n";
exit(-i);
template <class T> Set<T>::Set(const Set <T>& copy)
copy.th.e_desc -> references++;
^he^desc = copy.the_desc;
// скопированный счетчик
// ссылки элемента
// копируем указатель на desc
the_current = сору. th.e_desc -> root; // текущий элемент }
template <class Т> 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 [1];
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)
// память под обновляемый элемент// память под обновляемый элементthe_desc->references++;// скопированный счетчик ссылки эле:
// память под обновляемый элемент
// память под обновляемый элемент
release();
Ghe_desc = sl.the_desc; return *this;
}
template <class T> Set<T> operator + (Set<T>& si,Set<T>& s2)
Set<T> result;
si.first_element(); s2.first_element();
while(!si.is_at_end() I I !s2.is_at_end())
if(si.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());
si.next.element();
} else
if (si.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;
;emplate <class T> Set<T> operator * (Set<T>& si,Set<T>& s2) int flag = 0;
Set<T> intersection;
.first_element(); s2.first„element();
while(!si.is_at_end())
while((!s2.is_at_end()) && (!flag))
if(si.current.element() == s2.current_element()) {
intersect ion.add_at_end(si.current_element());
flag = 1;
}
.next_element();
}
flag = 0;
.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 < si.cardinality(); i++ )
s << s1.current„element();
s1.next_element();
if(i != si.cardinality()-1) s << ",";
}
s « '*)";
return s;
#endif
W ]4. Класс Symbolic
0noводный файл Mall.h включает классы Terms, Magnitude, Variable, 3t巳工,Function, Fsqrt, Fdf, Fint, Fexp, Fsinh, Fcosh, Fsin, Feos, Fin. ^ 0Б0Чный Msymbol.h файл включает классы Symbol, Sum, Product.
Открытый интерфейс класса Variable:
Variable。: конструктор по умолчанию.
.Variable(String):конструктор.
.Variable(String,T): конструктор.
.type О: возвращает символ 2 для указания типа объекта.
v ar Name () : возвращает имя переменной.
.oprint (ostreamfe): отправляет Variable в выходной поток.
.val(): численное значение Variable.
set (const Т): установка численного значения Variable.
Открытый интерфейс класса Number:
Number О: конструктор по умолчанию.
Number(Т): конструктор.
Оператор преобразования типа: operator Т()
Оператор присваивания:=
type() : возвращает символ 'N' для указания типа объекта.
varName() : возвращает строку NULL.
oprint (ostreamfe): отправляет Number в выходной поток.
val(): численное значение Number.
set (const Т): установка численного значения Number.
Открытый интерфейс классов Fsqrt, Fdf, Fint, Fexp, Fsinh, Fcosh., Fsin, Feos, Fin
varName () : возвращает имя функции FName.
oprint (): отправляет имя функции в выходной поток.
f (const Т&): численное значение функции.
type() : возвращает букву, указывающую на вид функции.
Открытый интерфейс класса Sum:
Sum(): конструктор по умолчанию.
Sum(const Т): конструктор для численных значений.
Sum(string, int): конструктор для символьных переменных.
clear (): удаление значения и зависимости переменной.
expand(): дистрибутивный закон и раскрытие биномов и мультиномов.
set (const Т): назначение численного значения переменной.
put (const Swn<T>&, const Sum<T>&): замена выражения на другое.
nvalue(): численное значение выражения.
value(): символьное значение выражения.
coef f (int): коэффициент при постоянном члене.
coef f (const Swn<T>&): коэффициент при переменном члене.
coef f (const Swn<T>&, int n): коэффициент при члене co степенью n.
depend(const Sum<T>&): объявление зависимости переменной.
nodepend (const Sum<T>&): объявление независимости переменной
isdepend(const Sum<T>&): проверка зависимости переменной.
is_Number(): проверка, является ли переменная числовым значением
df (const Sum<T>&,const Swn<T>&); первая производная выражения '
df (const Зшп<Т>&,const Sum<T>&,int n): n-я производная выражения
Int (const Зшп<Т>&, const Sum<T>&): интеграл от выражения.
Int (const Swn<T>&, const Sum<T>&, int n): n-й интеграл от выражения
power (const Зшп<Т>&,Т): функция возведения в целую степень.
exp (const Sum<T>&): экспоненциальная функция.
cosh(const Sum<T>&): функция гиперболического косинуса.
sinh(const Sum<T>&): функция гиперболического синуса.
cos (const Sum<T>&): функция косинуса.
sin(const Swn<T>&): функция синуса.
In (const Sum<T>&): функция логарифма.
sqrt (const Sum<T>&): функция квадратного корня.
Арифметические операторы: + (унарный), -(унарный), +, -, *,/, = +=
-=,*=- /=''
Операторы сравнения: ==, !=
• Потоковые операторы: >>, <<
Для детального описания структуры класса и каждого из методов, пожалуйста, обращайтесь к гл. 7.
// Mall.h
#ifndef ALL_H
#define ALL_H
#include < iostr earn. h.>
#include <math.h>
#include "MString.h"
template <class T> class Terms
public:
virtual 'Terms();
virtual char type() const = 0;
virtual String varName() const = 0;
virtual void oprint(ostream &) const = 0;
template <class T> class Magnitude : public Terms<T> public:
virtual "Magnitude();
virtual T val() const = 0;
virtual void set(const T) = 0;
};
template <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 type() const;
virtual String varName() const;
virtual void oprint(ostream &) const;
virtual T val() 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 "Number();
// оператор преобразования operator Т () const;
// методы
Number<T> & operator = (const Nmnber<T>&);
virtual char type() const;
virtual String varName() const;
virtual void oprint(ostreamfe) const;
virtual T val() const;
virtual void set(const T);
template <class Т> class Function : public Terms<T>
public:
virtual ~Function();
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 type() const;
virtual double f(const T &) const;
};
template <class T> class Fdf : public Function<T>
private:
static const char * const Fname;
public:
Fdf();
virtual ~Fdf();
virtual String varNameO const;
virtual void oprint(ostream &) const;
virtual char type() 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 varName() const;
virtual void oprint(ostream &) const;
virtual char type() const;
virtual double f(const T &) const;
};
ate <class T> class Fexp : public Function<T>
14 Vate:
public:FexpO ; virtual virtual virtualpublic:FexpO ; virtual virtual virtualvirtual virtualvirtual virtualУ static const char * const Fname;
public:
FexpO ; virtual virtual virtual
public:
FexpO ; virtual virtual virtual
virtual virtual
virtual virtual
~Fexp();
String varName() const;
void oprint(ostream &) const;
char type() const;
double f(const T &) const;
template <class T> class Fsinh. : public Function<T>
static const char * const Fname;
public:
FsinhO ;
virtual ~Fsinh();
virtual String varName() 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:
Fcosh();
virtual ~Fcosh();
virtual String varName() const;
virtual void oprint(ostream &) const;
virtual char type() const;
virtual double f(const T &) const;
fmplate <class T> class Fsin : public Function<T>
Private:
static const char * const Fname;
Public:
Fsin();
Virtual ~Fsin();
virtual String varNameO const;
virtual void oprint(ostream &) const;
virtual char type() const;
virtual double f(const T &) const;
template <class T> class Feos : public Function<T>
private:
static const char * const Fname;
public:
Feos ();
virtual ~Fco3();
virtual String varNameO const;
virtual void oprint(ostream &) const;
virtual char type() const;
virtual double f(const T &) const;
};
template <class T> class Fin : public Function<T>
private:
static const char * const Fname;
public:
Fln();
virtual ~F1r();
virtual String varNameC) const;
virtual void oprint(ostream &) const;
virtual char typeO const;
virtual double f(const T &) const;
};
// класс Terms template <class T>// класс Terms template <class T>Terms<T>::"Terms() {}Terms<T>::"Terms() {}
// класс Terms template <class T>
// класс Terms template <class T>
Terms<T>::"Terms() {}
Terms<T>::"Terms() {}
// класс Magnitude
template <class T> Magnitude<T>::*Magnitude() {}
// класс Variable
template <class T> Variable<T>: :Vciriable() : name(^^,^) ,value(T(0)) template <class T> Variable<T>: :Vciriable (String nm)
:name(nm), value(T(0)) {}
template <class T> Variable<T>: :Vciriable(String nm, T v)
:name(nni) , value (v) {}
template <class T> Variable<T>::Variable(const Variable<T>& v)
: name(v.name), value(v.value) {}
tcmpZate template template template templatetcmpZate template template template template<class T> Variable<T>: : ^VciriableO {}
tcmpZate template template template template
tcmpZate template template template template
<class T> void Variable<T>: : set (const T num) {value = num; }
<class T> char Variable<T>: :type() const { return W; }
<class T> String Variable<T>: iveirNanieO const{return name;}
<class T> void Vciriable<T>: : oprint (ostreamfe os) const
{ os << name;}
temp工ate 〈class T> Ttemp工ate 〈class T> T〃 класс Number template <class template <class template <classtemplate <class〃 класс Number template <class template <class template <classtemplate <classVariable<T>::val() const { return value; }
temp工ate 〈class T> T
temp工ate 〈class T> T
〃 класс Number template <class template <class template <class
template <class
〃 класс Number template <class template <class template <class
template <class
T> Number<T>::Number() : data(T(0)) {}
T> Number<T>::Number(T num) : data(num) {}
T> Number<T>::Number(const Number<T>& num) :data(num.dat a) {}
T> Number<T>::"Number() {}
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>: :vEirNanie() const { return ""; }
template <class T> void Number<T>::oprint(ostreamfe os) const
{ os << data; }
template <class T> TNumber<T>::val() const { return data; }
template <class T> void Number<T>: : set (const T num){ data = num; }
Function<T>::~Function() {}Function<T>::~Function() {}// класс Function template <class T>
Function<T>::~Function() {}
Function<T>::~Function() {}
// класс template template template template template template// класс template template template template template templateFsqrt
// класс template template template template template template
// класс template template template template template template
<class T> const char * const Fsqrt<T>::Fname = "sqrt";
<class T> Fsqrt<T>::Fsqrt() {}
<class T> Fsqrt<T>::'Fsqrt() {}
<class T> char Fsqrt<T>::type()const { return ,q,; }
<class T> String Fsqrt<T>::varName() const { return Fname;}
{ os << Fname; }template <class T> double Fsqrt<T>::f(const T& x) const { return sqrt(double(x)); }{ os << Fname; }template <class T> double Fsqrt<T>::f(const T& x) const { return sqrt(double(x)); }<class T> voidFsqrt<T>::oprint(ostreamfe os) const
{ os << Fname; }
template <class T> double Fsqrt<T>::f(const T& x) const { return sqrt(double(x)); }
{ os << Fname; }
template <class T> double Fsqrt<T>::f(const T& x) const { return sqrt(double(x)); }
〃 класс Fdf
template <class T> const char * const Fdf<T>::Fname = "df";
536
Глава 10. Листинги программ
template template template template template
<class T> Fdf<T>::Fdf() {}
<class T> Fdf<T>::~Fdf() {}
<class T> char Fdf<T>::type()const { return 〉d〉・}
<class T> String Fdf<T>::varName() const { return Fname- }
<class T> voidFdf<T>::oprint(ostreamfe os) const
{ os << Fname; }
template
<class T> double Fdf<T>::f(const T&) const { return 0.0; }
// класс template template template template template template
FInt
<class T> const char * const FInt<T>::Fname = "Int";
<class T> FInt<T>::FInt() {}
<class T> FInt<T>::"FInt() {}
<class T> char FInt<T>: :type()const { return 2; }
<class T> String FInt<T>: :vcirNanie() const { return Fname; }
<class T> void FInt<T>::oprint(ostreamfe os) const
{ os << Fname; }
template
<class T> double FInt<T>::f(const T&) const { return 0.0; }
// класс Fexp
template <class T> const char * const Fexp<T>::Fname = "exp";
template <class T> Fexp<T>::Fexp() {}
template <class T> Fexp<T>::-Fexp() {}
template <class T> char Fexp<T>: :type()const { return У; }
template <class T> String Fexp<T>::varName() const { return Fname; }
template <class T> void Fexp<T>::oprint(ostreamfe os) const
{ os << Fname; }
template <class T> double Fexp<T>::f(const T& x) const
{ return exp(double(x)); }
// класс Fsinh
template <class T> const char * const Fsinh<T>::Fname = "sinh";
template <class T> Fsinh.<T>: :Fsinh() {}
template <class T> Fsinh<T>::*Fsinh() {}
template <class T> char Fsinh<T>::type()const { return 'j'; }
template <class T> String Fsinh<T>::varName() const { return Fname;}
template <class T> void Fsinh<T>::oprint(ostreamfe os) const
{ os << Fname; }
template <class T> double Fsinh<T>::f(const T& x) const
{ return sinh(double(x)); }
// класс
Fcosh
template
<class
T>
const char * const Fcosh<T>::Fname = "cosh"
template
<class
T>
Fcosh<T>::Fcosh() {}
template
<class
T>
Fcosh<T>::~Fcosh() {}
template template templatetemplate template template<class T> char Fcosh<T>: :type()const { return W; }
template template template
template template template
<class T> String Fcosh<T>: :vcirNanie() const { return Fname;}
<class T> void Fcosh<T>::oprint(ostreamfe os) const
template <class T> double Fcosh<T>::f(const T& x) const { return cosh(double(x)); }template <class T> double Fcosh<T>::f(const T& x) const { return cosh(double(x)); }{ os << Fname; }
template <class T> double Fcosh<T>::f(const T& x) const { return cosh(double(x)); }
template <class T> double Fcosh<T>::f(const T& x) const { return cosh(double(x)); }
II класс Fsin
template <class T> const char * const Fsin<T>::Fname = "sin";
template <class T> Fsin<T>::Fsin() {}
template <class T> Fsin<T>::*Fsin() {}
template <class T> char Fsin<T>: :type()const { return 2; }
template <class T> String Fsin<T>::varName() 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)); }
// класс Feos
template
<class
T>
const £
:har * const Fcos<T> : : Fname = "cos";
template
<class
T>
Fcos<T)
»::Feos () {}
template
<class
T>
Fcos<T)
»::~Fcos() {}
template
<class
T>
char
Fcos<T>::type()const { return ? cJ; }
template
<class
T>
String
Fcos<T>::varName() const { return Fname; }
template
<class
T>
void
Fcos<T>::oprint(ostreamfe 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>: <Fln() {}
template
<class
T>
char Fln<T>::type()const { return У; }
template
<class
T>
String Fln<T>: :VcirNanie() const { return Fname; }
template
<class
T>
void Fln<T>::oprint(ostreamfe os) const
{ 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
#include #include #include #include #include #include #include#include #include #include #include #include #include #include<assert.h> <iostream.h> <stdlib.h> <ctype.h> "MString.h" "Mall.hn "MList.h"
#include #include #include #include #include #include #include
#include #include #include #include #include #include #include
typedef Nwnber<int> Integer;
typedef Number<double> Double;
// опережающее объявление
template <class T> class Sum;
template <class T> class Product;
// объявление и определение класса Symbol
template <class T>
class Symbol
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 removeB();// удаление переменных с type()==,B?
int simpFuncs(Symbol<T>**,char);// упрощение элементарных функции Symbol<T>*zero() const; // специальная функция для константы 。
Symbol<T>*one() const;// специальная функция для конснанны 1
void Shrink();// упрощение выражения
// чисто виртуальные внутренние функции
virtual chartype() const = 0;
virtual Sum<T> val() const = 0;
virtual Tnval() const = 0;
virtual int virtual int virtual int virtual int virtual void virtual void virtual void virtual void
replacel(Symbol<T> *, Symbol<T> *)= equal.index(Symbol<T>*) = 0;
is_equal(Symbol<T> *) const = 0;
gather() = 0;
copy(Symbol<T> **) const = 0;
oprint(ostream &) const = 0;
deleteOneNodeO = 0;// удаляем толъ
deleteAHNodes() = 0; // удаляем все // исходящие ие
0;
>ко этот узел узлы, j этого узла
virtual int
mxpand(Symbol<T> **) = 0;
virtual int
dxpand(Symbol<T> **) = 0; // дистрибутивный закон
virtual void
diff(Symbol<T> **, Symbol<T> *) const
;=0;
virtual void
integrate(Symbol<T> **, Symbol<T> *)
const = 0;
virtual int
funcSimp(int &, 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
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> **);
virtual int
dxpand(Symbol<T> **);
virtual void
diff(Symbol<T> **, Symbol<T> *) const;
virtual void virtual int
integrate(Symbol<T> **, Symbol<T> *) a funcSimpCint &, 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>
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 type() 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
deleteOneNode();
virtual void
deleteAHNodes ();
virtual int mxpand(Symbol<T> **);
virtual int virtual void virtual void virtual int
dxpand(Symbol<T> **);
diff(Symbol<T> ** , Symbol<T> *) const; integrate(Symbol<T> **, Symbol<T> *) const; funcSimp(int &, int &);
virtual int virtual int virtual int virtual int virtual int
shrink_l();
shrink_2();
shrink_3(Symbol<T> **);
shrink_4();
shrink_5();
// поля данных int is.Bound, is_Set; Terms<T> *data; List<Sum<T>*> deplist;
public:
// конструкторы Sum();
Sum(const T);
Sum(int, int);
Sum(String, int);
Sum(const Sum<T>&);
~Sum();
// закрытые функции, void clear();void expand();void set(const T);// закрытые функции, void clear();void expand();void set(const T);доступные только Sum<T>
// закрытые функции, void clear();
void expand();
void set(const T);
// закрытые функции, void clear();
void expand();
void set(const T);
// обновление использованной переменной
// раскрытие скобок выражения
// присвоение значения переменной
int put(const Sum<T>&, const Sum<T &);
〃
присвоение выражения другому выражению
T
nvalue() const;//
возвргицает
численное значение переменной
Sum<T>
value() const; //
возвргицает
значение выражения
T
Swn<T>
coeff(int) const;
coeff(const Sum<T>&) const;
// возвргицает коэффициент // члена-константы
// возвргицает коэффициент
Sum<T>
// при члене
coeff(const Sum<T>&,int) const; // возвргицает коэффициент
// при члене co степенью n.
void depend(const Sum<T>&);// объявл. зависимости перем.
void nodepend(const Sum<T>&); // объявл. независимости перем, int isdepend(const Sum<T>&) const; // проверка зависимости перем, int is_Nwnber() const; // проверка, переменная - чисто число?
// арифметические операторы Sum<T>& operator = (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 ==
(const Sum<T>&, const Sum<T>&);
friend int operator !=
(const Sum<T>&, const Sum<T>&);
Swn<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T>Swn<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T>operator operator operator operator operator operatoroperator operator operator operator operator operator+ () const;+ () const;-()const;-()const;+=+=*=/=*=/=(const Sum<T>&);(const Sum<T>&);(const Sum<T>&);(const Sum<T>&);(const Sum<T>&);(const Sum<T>&);(const Sum<T>&);(const Sum<T>&);
Swn<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T>
Swn<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T>
operator operator operator operator operator operator
operator operator operator operator operator operator
+ () const;
+ () const;
-()const;
-()const;
+=
+=
*=
/=
*=
/=
(const Sum<T>&);
(const Sum<T>&);
(const Sum<T>&);
(const Sum<T>&);
(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 friendfriend friend friend friend friend friend friend friend friend friend friend friendSum<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T>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>&);
friend friend friend friend friend friend friend friend friend friend friend friend
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>
Sum<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T> Sum<T> 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> * Symbol<T>::zero() const;
friend Symbol<T> * Symbol<T>::one() const;
friend int Product<T>::mxpand(Symbol<T> **);
friend void Product<T>::integrate(Symbol<T>**,Symbol<T>*) c
// определение для класса Symbol
template <class T> Symbol<T>::Symbol()
:branches(0), 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 нулевое, ничего не делаем
if {if {(num)(num)ep = new Symbol<T>*[num]; fac^exp = new T[num]; next_var = new int[num];ep = new Symbol<T>*[num]; fac^exp = new T[num]; next_var = new int[num];assert(ep != NULL);assert(fac_exp != NULL); assert (next_vcir !== NULL);assert(ep != NULL);assert(fac_exp != NULL); assert (next_vcir !== NULL);
if {
if {
(num)
(num)
ep = new Symbol<T>*[num]; fac^exp = new T[num]; next_var = new int[num];
ep = new Symbol<T>*[num]; fac^exp = new T[num]; next_var = new int[num];
assert(ep != NULL);
assert(fac_exp != NULL); assert (next_vcir !== NULL);
assert(ep != NULL);
assert(fac_exp != NULL); assert (next_vcir !== NULL);
// инициализируем ep[] в NULL, fac_exp[] в 1 и next_var[] в 0 for (int i=0; i<num; 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)
ep = new Symbol<T>*[num]; fac_exp = new T[num];next,var = new int[num];ep = 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);assert(ep != NULL); assert(fac.exp (= NULL); assert(next_var != NULL);
ep = new Symbol<T>*[num]; fac_exp = new T[num];
next,var = new int[num];
ep = 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);
assert(ep != NULL); assert(fac.exp (= NULL); assert(next_var != NULL);
// инициализируем ep[] в 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]; fac_exp = new T[num]; next_var = new int[num];ep = 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);assert(ep != NULL);assert(fac_exp != NULL); assert(next„var != NULL);
ep = new Symbol<T>*[num]; fac_exp = new T[num]; next_var = new int[num];
ep = 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);
assert(ep != NULL);
assert(fac_exp != NULL); assert(next„var != NULL);
// инициализируем ер [] и fас_ехр[] в соответствующие зн^ч
for (int i=0; Knum; i++)电“”兄
ep[i] 工 newepLi];
fac_exp[i] = new_fe[i];
next_var [i] = new_nx [i];
}
}
}
// деструктор
template <class T> Symbol<T>::"Symbol() {}
// удаляем связь и используемую память
// Замечание: число ветвей уменьшается на 1,
//индекс есть позиция удаляемого узла
template <class Т> void Symbol<T>::remove(int index)
int k, m;
Symbol<T> **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 ();
delete □ ep; delete □ fac_exp; delete [] next_var;
// назначаем новое месторасположение
ер = ept;
fac_exp = fajexpt;
next_var = next.vart;
// удаляем переменные c type()==;B? (определенные переменные) template <class T> void Symbol<T>::removeB()
int i ;
for (i=0; i<branches; i++)
{
if (ep[i]->type() == ’B,)
ep[il->ep[0]~>copy(ep+i);
if (ep[i]->type() == ‘V’) next_var[i] = 1;
}
ep[i]->removeB();
if (Jep[i]->branches)
{
if ((ep[i]->type() == ^d1) I I (ep[i]->type()== ‘廿〉)
// это - оператор дифференцирования или интегрирования
if (ер[i]->ер[0]->type() == ,В,)
ер[il->ер[0]->ер[0]->сору(ер[i] ->ер); // удалить узел типа В
if (ер[i]->ер[1]->type() ==,В,〉
ер[i]->ер[1]->ер[0]->сору(ер[i]->ер+1);//удалить узел типа ,В,
if (ер[i]->ер[0]->type() == V) ер[i]->next_var[0] = 1;
if (ep[i] ->ep[l]->type()==,『)ep[i] ->next_var [1] = 1;
if(ep[i]->type() == 'd')
ep[i]->ep[0]->diff(ep+i,ep[i]->ep[1]);
else // type == F
ep[i]->ep [0]->integrate(ep+i,ep[i]->ep[l]);
else if (islower(ep[i] ->type())) // это - функция
if (ep[i]->ep[0]->type() == ’B,)
ep[i]->ep[0]->ep[0]->copy(ep[i]->ep);// удалить узел типа ,B, if (ер[i] ->ер[0] ->type()== ‘屮)ep[i] ->next_var [0] = 1;
ep[i]->ep[0]->Shrink();
ep[i]->simpFuncs(ep+i, ep[i]->type());
}
// упрощение элементарных функций
template <class T> int Symbol<T>::simpFuncs(Symbol<T> **s, char m
{"
switch (T) case :
if (ep[01 ->bran.ch.es=:=l && ep[0]->ep[0] ==zero())
{ (*s)二 one(); return 1; } break;
case 'j':
if (ep[0]->branches==l && ep[0]->ep[0]==zero())
{ (*s) = zero(); return 1; } break;
case 'w‘:
if (ep[0]->branches==l && ep [0]->ep[0]==zero())
{ (*s) = one(); return 1; } break;
case :
if (ep[0]->branches==l && ep[0]->ep[0]==zero())
{ (*s)二 zero(); return 1; } break;
case 'c':
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) = zero(); return 1; break; case 'q‘:
}
}
{ (*s) = zero(); return 1 ;扌 && ep[0] ->fac_exp[0]==以°{ (*s) = zero(); return 1 ;扌 && ep[0] ->fac_exp[0]==以°if (ep[0]->branches==l)
{ (*s) = zero(); return 1 ;扌 && ep[0] ->fac_exp[0]==以°
{ (*s) = zero(); return 1 ;扌 && ep[0] ->fac_exp[0]==以°
if (ep[0]->ep [0]==zero())
else if(ep[0]->ep [0]==one()
{ (*s) = one(); return 1; }
} break; default:;
return 0;
}
t;e <class T> Symbol<T> * Symbol<T>: :zero() const
(tatic Symbol<T> *addr = new Sum<T>(T(0),char());
Assert(addr != NULL);
return addr;
mplate <class T> Symbol<T> * Symbol<T>::one() const
〈static Symbol<T> *addr = new Sum<T>(T(l),char());
assert(addr != NULL);
return addr;
// определение класса Sum
I/ открытие уэла Sum, который не содержит данных template <class Т> Sum<T>: : Sum()
:Symbol<T>(0) , is_Bound(0), is_Set(0) , data(new Variable<T>)
{ assert(data != NULL); }
// открытие узла Sum, который имеет n ветвей
template <class T> Sum<T>::Sum(int n, inT)
:Symbol<T>(n), is_Bound(0), is_Set(0), data(new Variable<T>)
{ assert(data != NULL); }
// открытие узла Number co значением num
template <class T> Sum<T>: :Sum(const T num)
:Symbol<T>(1),is_Bound(0),is_Set(0),data(new Variable<T>("",num)) {
assert(data != NULL);
if (num == T(0)) ep[0] = zero();
else
{
ep[0] = one();
fac_exp[0] = num;
}
} next_var[0] = 1;
template <class T> Sum<T>::Sum(const T num, char)
:Symbol<T>(0) , is_Bound(0), is_Set(0), data(new Number<T>(num))
'assert(data != NULL); }
template <class T> Sum<T>::Sum(char ftype, char)
:Symbol<T>(2), is_Bound(0), is_Set(0)
case
data =
new Fdf<T>;
break;
case
data =
new FInt<T>;
break;
case
data =
new Fexp<T>;
break;
case
data =
new Fsinh<T>;
break;
case
data =
new Fcosh<T>;
break;
case
'z'
data =
new Fsin<T>;
break;
case
'c'
data 二
new Fcos<T>;
break;
case
data =
new Fln<T>;
break;
case
'q'
data =
new Fsqrt<T>;
break;
default
switch, (ftype) {switch, (ftype) {
switch, (ftype) {
switch, (ftype) {
assert(data 1= NULL);
branches = 0;// это указывает, что это - концевая вершина
template <class Т>
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(0), is_Set(0), data(new Variable<T>(nm))
{ assert(data != NULL); }
template <class T> Sum<T>: :Sum(String new_name, inT) | :Symbol<T>(0), is_Bound(0), is_Set(0),
data(new Variable<T>(new_name))
{ assert(data != NULL); }
template <class T> Sum<T>::Sum(const Sum<T> &s)
:Symbol<T>(1), is_Bound(0), is.Set(0), data(new Variable<T>)
assert(data != NULL);
if (s.typeO == 'B') s . ep [0] ->copy (ep);
elses.copy(ep);
if (s*type() == V) next_.var [0] = 1;
template <class T> Sum<T>: : "SumO
if (! branches)
if (islower(type())) ep[0]->deleteAHNodes(); deleteOneNode();
else deleteAHNodes ();
deplist. deleteAHNodes ();
}
temPtemPlate <class T> Sum<T> & Sum<T>::operator =late <class T> Sum<T> & Sum<T>::operator =(const Sum<T> &s(const Sum<T> &s
temP
temP
late <class T> Sum<T> & Sum<T>::operator =
late <class T> Sum<T> & Sum<T>::operator =
(const Sum<T> &s
(const Sum<T> &s
if (this == &s) return *this; ((Sum<T>&)s).Shrink();
// удаляем старое значение *this, если оно было назначено. 〃 removeBO важна, потому что если она не включена, то в // у = а+b; а = х; у = с;
// перед у = с будет освобождена вся старая память у,
// которая включает память а; тем самым будет потеряно х:
// у -> ""->а->х
〃->ь
if (is.Bound) { removeBO; ep[0]->deleteAHNodes(); } delete [] ep; delete [] fac_exp; delete [] next_var; branches = 1;
ep = new Symbol<T>* [1]; fac_exp = new T[l]; next_var = new int[1]; is_Bound = 1; is_Set = 0; next_var[0] = 0;ep = new Symbol<T>* [1]; fac_exp = new T[l]; next_var = new int[1]; is_Bound = 1; is_Set = 0; next_var[0] = 0;assert(ep != NULL);assert(fac_exp != NULL); assert(next^var != NULL);assert(ep != NULL);assert(fac_exp != NULL); assert(next^var != NULL);
ep = new Symbol<T>* [1]; fac_exp = new T[l]; next_var = new int[1]; is_Bound = 1; is_Set = 0; next_var[0] = 0;
ep = new Symbol<T>* [1]; fac_exp = new T[l]; next_var = new int[1]; is_Bound = 1; is_Set = 0; next_var[0] = 0;
assert(ep != NULL);
assert(fac_exp != NULL); assert(next^var != NULL);
assert(ep != NULL);
assert(fac_exp != NULL); assert(next^var != NULL);
if (s.type() == '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] = 1;
fac_exp[0] = T(l);
expand();
return *this;
// возвращает тип уэла указываемого указателем
:emplate <class T> char Sum<T>::type() const
if (branches)
if (is_Bound) return B;
return 'S';
}
}}return data->type();
}
}
// печать численного значения выражения
template <class Т> Sum<T> Swn<T>::val() const
int i;
Sum<T> s(T(0));
// печать значения, если это - концевой узел if (!branches)
if (islower(type())) // это - функция
if (((Sum<T>*)ер[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() == W) r.next_var[0] = 1;
return r;
}
else
if (is_Set) return Sum<T>(((Magnitude<T>*)data)->val());
elseelsereturnreturn*this;*this;
else
else
return
return
*this;
*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>:treplacel(Symbol<T> *slsSymbol<T> *s2)
int i, index, flag=0;
for (i=0; Kbranches; i++) flag += ep[i]->replacel(sl,s2);
if (islower(type()))
flag +=((Sum<T>*)ep[0])->put(*((Sum<T>*)si),*((Sum<T>*)s2));
if (sl->braiiches >= 1)
index = equal_index(sl);
if (index >= 0)
s2~>copy(ep+index); fac_exp[index] = T(l); flag++;
else
for (i=0; Kbranches; i++) if (ep[i]->is_equal(si)) { s2->copy(ep+i); flag++; }
return flag;
}
// возвращаем индекс равного компонента
// возвращаем (-1), если такого компонента не найдено template <class Т> int Sum<T>: :equal_index(S3rmbol<T> *) { return (-1); }
// проверяем, равно ли 2-е выражение
template <class Т> int Sum<T>::is_equal(Symbol<T> *s) const {
int i,k;
int *ip ;
// если s - узел product, возвращаем 0
if (s->type() == P〉 возвращаем 0;
// если s эквивалентно this, возвращаем 1
if (s == (Symbol<T>*)this) возвращаем 1;
〃 если оба - концевые листы, a s не равно this,
//то проверяем функцию
if (!s->branches && !branches)
{
if (islower(type()) && s->type() == type())
if ((type() == ?dJ) I I (type()==,廿))
return (ep[0]->is_equal(s->ep[0]) && ep[1]->is_equal(s->ep[1]));
return ep[0]->is_equal(s->ep[0]);
}
else 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++)
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 Т> 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(0)) if (branches > 2) { remove(j) ; remove(i); j=i;} else // branches == 2-> удаляем этот член {// и создаем узел zero()
remove(j); j=i;
ep [0] = zero();
fac_exp[0] = T⑴;
{ remove(j); j=i; }{ remove(j); j=i; }else
{ remove(j); j=i; }
{ remove(j); j=i; }
return flag;
}
// копируем поддерево отсюда в sp
template <class Т> 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; 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);
else
if (islower(data->type()))
*sp = new Sum<T>(data->type(),char()); assert(*sp != NULL);
ep[0]->copy((*sp)->ep);
if ((data->type() == ;dJ)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(data->type() == J iJ))
{
os << " ('* ;
if (ер[0]->branches==l && ер[0]->fас„ехр[0] == Т(1)) ер [0]->ер [0]->oprint(os);
else ер [0]->oprint(os);
os « ",";
ep[1]->oprint(os); os << ")";
else ep[0]->oprint(os);
else
os << " ('* ;
// не печатаем + т.к. это ~ первый член
if (fас_ехр[0] == Т(-1)){ os « ”-"; ер[0]->oprint(os) ; }
else if (fac_exp[0] != T(l))
os << fac_exp[0];
if (ep [0] != one()){ os <<; ep[0]->oprint(os) ; }
else ep[0]->oprint(os);
// печатаем остальные члены for (int i=l; i<branches; i++)
if (fac_exp[i] > T(0))
os << "+”;
ep[i]->oprint(os);ep[i]->oprint(os);os « "*"; ep[i]->oprint(os) ; }os « "*"; ep[i]->oprint(os) ; }os << "-,,; ep [i]->Qprint (。5);os << "-,,; ep [i]->Qprint (。5);os <<; ep[i]->oprint(os);〉os <<; ep[i]->oprint(os);〉if (fac_exp[i] == T(l)) else
ep[i]->oprint(os);
ep[i]->oprint(os);
os « "*"; ep[i]->oprint(os) ; }
os « "*"; ep[i]->oprint(os) ; }
os << "-,,; ep [i]->Qprint (。5);
os << "-,,; ep [i]->Qprint (。5);
os <<; ep[i]->oprint(os);〉
os <<; ep[i]->oprint(os);〉
os << fac_exp [i];
if (ep[i] != one()) {
else // fac_exp[i] < T(0)
if (fac_exp[i] == T(T)) { else
os << f ac__exp [i];
if (ep[i] != one()) {
os « ")";
// удаляем только этот узел
template <class Т> void Sum<T>::deleteOneNode()
delete data;
delete □ ep; delete □ fac_exp; delete [] next_var;
// уделяем все узлы, исходящие иэ него
template <class Т> void Sum<T>: :deleteAHNodes() {
if (branches)
for (int i=0; i<branches; i++)
if (!next_var [i] ) ep [i] ->deleteAHNodes();
deleteOneNode();
else if (islower(type()))
ep [0] ->deleteAHNodes ();
deleteOneNode();
// раскрытие биномов и мультиномов
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; i<branches; i++) flag += ep[i]->dxpand(ep+i); return flag;
}
// дифференцирование
template <class T> void Sum<T>::diff(Symbol<T>**s,Symbol<T>*t
int i;
if (branches)
*s 二 new Sum<T>(branches,0); assert(*s != NULL);
// копируем все коэффициенты
for (i=0; Kbranches; i++) (*s)->fac_exp[i] = fac_exp[i];
// дифференцируем все члены
for (i=0; i<branches; i++)
ep[i]->diff((*s)->ep+i, t);
if ((*s)->ep[i]->type() == ?V> I I (*s)->ep[i]->type() ==〉w) (*s)->next_var[i] = 1;
else (*s)->next_var[i] = 0;
else // type()==,V? или ?№ или Functions,dx/dx=l,dy/dx=0,dC/dx=0
if (type() == d) // это - оператор дафференцирования
// (d/dx) df (у,х) = df (df (у,х) ,х)
*s = new Sum<T>(?d,,char О);assert(*s != NULL);
copy((*s)->ep);
t->copy((*s)->ep+l);
else if (type() == X)
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>(Jd?,char()); assert(*s != NULL);
copy((*s)->ep);
t->copy((*s)->ep+l);
else *s = zero();
else if (islower(typeO)) // это - функции
*s = new Sum<T>(1,0);assert((*s) != NULL);
(*s)->ep[0] = new Product<T>(2); assert((*s)->ep[0] != NULL);
ер[0]->diff((*s)->ep[0]->ep,t)
diffRules((*s)->ep[0]->ep+l, type());
else if (type() == ,N‘) *s 二 zero();
else // type() ==
if ((Symbol<T>*)this == t) *s = one();
else if (isdepend(*((Sum<T>*)t)))
*s = new Sum<T>(1dJ,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 I I (*s)->ep[i] ->type() == >№ ) (*s)->next_var[i] = 1;
else (*s)->next_var[i] = 0;
else // type() == V или ,N, или функции
if (type() == У) // это - оператор интегрирове^ния
// Int(Int(у,х),х)
*s = new Sum<T>(? iJ,char()); assert(*s != NULL);
copy((*s)->ep);
t->copy((*s)->ep+l);
else if (type() == >d>) // это - оператор дифференцирования
// Int (df(y,x),x) = у if (ep[ll == t)
*s=new Sum<T>(1,0);
ep[0]_>CQpy((*s)->ep);
// Int (y(x),x)
else if(isdepend(*((Sum<T>*)t)))
assert(*s != NULL);assert(*s != NULL);assert((*s) != NULL); assert((*s) != NULL);assert((*s) != NULL); assert((*s) != NULL);*s = new Sum<T>(J iJ,char());
assert(*s != NULL);
assert(*s != NULL);
assert((*s) != NULL); assert((*s) != NULL);
assert((*s) != NULL); assert((*s) != NULL);
copy((*s)->ep);
t->copy((*s)->ep+l);
else // Int(y,x)=yx
*s = new Sum<T>(l,0);
(*s)->ep [0] = new Product<T>(2);
copy((*s)->ep[0]->ep);
t->copy((*s)->ep [0]->ep+l);
else if (islower(type())) // это - функции
Symbol<T> *p=ep [0];
while((p->branches==l)&&(p->type()!=,V5)) p=p->ep [0];
if (p == t)
IntRules(s, type ());
else if (isdepend(*((Sum<T>*)t)))
*s = new Sum<T>(JiJ,char());assert(*s != NULL);
copy((*s)->ep);
t->copy((*s)->ep+l);
else // Int(y,x)=yx
*s = new Зшп<Т>(1д0);assert((*s) != NULL);
(*s)->ep [0] = new Product<T>(2); assert((*s) != NULL);
copy((*s)->ep[0]->ep);
t->copy((*s)->ep[0]->ep+l);
else if (type() == J№)
// int(0,x)=0
if(((Number<T>*)data)->val()==T(O)) *s = zero();
else // int(C,x)=Cx
*s 二 new Sum<T>(l,0);assert((*s) != NULL);
t->copy((*s)->ep);
else // type() == V
if ((S3rmbol<T>*)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)/T(2);
(*s)->ep[0]->fac_exp [0] = T(2);
t->copy((*s)->ep[0]->ep);
}
else if (isdepend(*((Sum<T>*)t)))
*s = new Sum<T>(1i1,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>(2); 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&s int&) { return 0; }
// максимальное упрощение алгебраического выражение template <class Т> void Symbol<T>::Shrink()
int i, flag;
if (type() == JBJ) ep[0]->Shrink(); else
removeBO ;
do
do
}}}}} }} }〃〃flag for for for for forflag for for for for for= 0; (i=0; (i=0; (i=0; (i=0; (i=0;= 0; (i=0; (i=0; (i=0; (i=0; (i=0;i<branches; i++) i<branches; i++) i<branches; i++) i<branches; i++) i<branches; i++)i<branches; i++) i<branches; i++) i<branches; i++) i<branches; i++) i<branches; i++)flag flag flag flag flagflag flag flag flag flag+= += += +=+=+= += += +=+=shrink_2();shrink_5();shrink_3(ep+i);shrink」();shrink_4();shrink_2();shrink_5();shrink_3(ep+i);shrink」();shrink_4();while(flag);// повторный цикл, еслиwhile(flag);// повторный цикл, еслиwhile(gather()); // повторный цикл, еслиwhile(gather()); // повторный цикл, еслиесть что упрощать есть комбинация члено:есть что упрощать есть комбинация члено:возвращаем коэффициентвозвращаем коэффициентtemplate <class T>template <class T>Sum<T>Sum<T>при sSum<T>::coeff(constпри sSum<T>::coeff(constSum<T> &s) constSum<T> &s) const
}
}
}
}
} }
} }
〃
〃
flag for for for for for
flag for for for for for
= 0; (i=0; (i=0; (i=0; (i=0; (i=0;
= 0; (i=0; (i=0; (i=0; (i=0; (i=0;
i<branches; i++) i<branches; i++) i<branches; i++) i<branches; i++) i<branches; i++)
i<branches; i++) i<branches; i++) i<branches; i++) i<branches; i++) i<branches; i++)
flag flag flag flag flag
flag flag flag flag flag
+= += += +=
+=
+= += += +=
+=
shrink_2();
shrink_5();
shrink_3(ep+i);
shrink」();
shrink_4();
shrink_2();
shrink_5();
shrink_3(ep+i);
shrink」();
shrink_4();
while(flag);// повторный цикл, если
while(flag);// повторный цикл, если
while(gather()); // повторный цикл, если
while(gather()); // повторный цикл, если
есть что упрощать есть комбинация члено:
есть что упрощать есть комбинация члено:
возвращаем коэффициент
возвращаем коэффициент
template <class T>
template <class T>
Sum<T>
Sum<T>
при s
Sum<T>::coeff(const
при s
Sum<T>::coeff(const
Sum<T> &s) const
Sum<T> &s) const
int i, j, k, m, found;
Symbol<T> *ptr, *sptr, *mptr;
Sum<T> result(T(0));
// перед выделением коэффициента сначала упрощаем выражение
((Sum<T>*)this)->Shrink();
((Sum<T>&)s).Shrink();
// x.coeff(x) -> 1
if(!branches &&
(&s==this I I
(s.type() ==,B‘ && s.ep[0]->type() ==&&
ep [0]->branches == 1 && s.ep[0]->ep [0] == (Symbol<T>*)this))
{ result = T(l); return result; }
// инициализация различных указателей: ptr, sptr, mptr // в зависимости от разных ситуаций
if (type() == В)
ptr = ер[0];
if (s.type() == В)
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 = (Symbol<T>*)(&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 = (Symbol<T>*)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 = (Symbol<T>*)(&s);
if (mptr->type() == >S‘ && mptr->bran.ches == 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() == JPJ)
// выделяем память для записи индексов парных членов 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]=-1 для индексации)
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];
tempsum.ep[0]->next_var[m] = ptr->ep[i]->next_var[k];
m++;
// добавим этот член к собираемому коэффициенту result += Sum<T>(ptr->fас_ехр[i]) * tempsum;
}
delete □ index;
else // если s типа V или J№
if (! ptr->branches && ptr->is_equal(mptr)) result += T(l);
for (i=0; i<ptr->branches; i++)
// если выражение состоит из одного члена
if (ptr->ep[i]->type() != ’P,)
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];
tempsum.ер[0]->next_var[m] = ptr->ep[i]->next_var[^1 result += Sum<T>(ptr->fac_exp[i]) * tempsum; break;
}
return result;
// возвращаем коэффициент члена - константы
template <class T> T Sum<T>::coeff(int) const
int i;
T result;
((Sum<T>*)this)->Shrink();
if (type() == B)
for (i=0; i<ep[0]->branches; i++)
if (ep[0]->ep[i] == one()) return ep[0]->fac_exp[i]; else
for (i=0; i<branches; i++) if (ep[i] == one())
result = fac_exp [i]; return result;
return T(0);
// возвращаем коэффициент при s"n
// заметим, что s должен иметь type() == V
template <class T> Sum<T> Sum<T>::coeff(const Sum<T> &s,int n) const
if (n == 1) return coeff(s);
else if (n)// если n I= 0,1
Sum<T> news(1,0);
Product<T> *p = new Product<T>(l); assert(p != NULL);
news.ep [0] = p;
s.copy(news.ep [0]->ep);
news.ep[0]->fac.exp[0] = T(n);
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(0));
// инициализации различных указателей: ptr, sptr if (type() == ,B,)
ptr = ep[0];
if (s.type() == B) sptr = s.ep[0];
elsesptr = (Symbol<T>*)(&s);
if (sptr->type() == S && sptr->branches == 1
sptr->fac_exp[0] == T(l)) sptr = sptr->ep[0]; else
ptr = (Symbol<T>*)this;
if (s.type() == B) sptr = s. ep [0];
elsesptr = (Symbol<T>*)(&s);
if (sptr->type() == 'S' && sptr->branches == 1
sptr->fac_exp[0] == T(l)) sptr = sptr->ep[0];
if (!(ptr->branches I I 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;
}
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.init(); !m; ++m) if (m() == &x) {
m.delete_Current(); return;
// проверка скрытной зависимости переменных template <class T>
int imp1icit_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++)
// проверка скрытной зависимости переменных и сумм
if ((s[i]->type()==,S,) I 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] ->branch.es==O)): return 1;
return 0;
}
// проверка зависимости переменных
template <class T> int Sum<T>::isdepend(const Sum<T> &x) const
{
if (type() == J№) 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.init(); !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 Т> int Sum<T>::is_Number() const
Symbol<T> *ptr;
if (type() == ^^) ptr = ep[0];
else ptr = (Symbol<T>*)this;
if (ptr->branches == 1 && ptr->ep[0]->type() == ,N,) return 1; return 0;
}
/ / экспоненциальная функция
template <class T> Sum<T> exp(const Sum<T> &s)
Sum<T> r(,e,,char());
((Sum<T>&)s) .ShrinkO ;
if (s.typeO ==,B,)s . ер [0] ->copy (г. ер);
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> sinh(const Sum<T> &s)
Sum<T> r(Jj J,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] = 1;
return r;
// функция гиперболического косинуса
template <class T> Sum<T> cosh(const Sum<T> &s)
Sum<T> rC^w^,char());
((Sum<T>&)s).Shrink();
if (s.type() == ;BJ) s.ep[0]->copy(r.ep);
else if (s.branches) s.copy(r.ep);
else
r.ep[0] = new Sum<T>(i,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> sin(const Sum<T> &s)
Sum<T> г ( Jzг , 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>(1,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(JcJ,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> ln(const Sum<T> &s) {
Sum<T> r(5l>,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 Т> 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]二 1;
return r;
// возведение суммы s в степень n
template <class T> Sum<T> power(const Sum<T> &s, T n) {
static T zero(0);
Sum<T> r(l,0);
if (n != zero)
Product<T> *p = new Product<T>(1);
r.ep [0] = p;
// сначгша упрощаем выражение перед возведением в ст ((Sum<T>&)s).Shrink();
if (s.type() == ,В,) s.ер[0]->сору(г.ер[0]->ер);
elses.сору(г.ер [0]->ер);
if (s.type() == V) r.ep[0]->next_var[0] = 1;
г.ер[0]->fас_ехр [0] = п;
else
г > ер [0] = s.one();
r.fac_exp [0] = Т(1);
return г;
}
// возводим Т х в степень п
template <class Т> Т power(Т х, Т п)
int N;
T Y(l);
if (n < T(0))
x = T(l)/x;
N = int(-n);
else
N = int(n);
if (N==0) return T(l);
while(l)
if (N%2)
Y = 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
Swn<T> result(1,0);
Symbol<T> *tt;
((Sum<T>&)s) . ShrinkO ;
if (t.type() == }BJ) tt = t.ep[0];
elsett = (Symbol<T>*)&t;
if (tt->type()==J SJ && tt->branches == 1 && tt->fac_exp[0]==T(1)) 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> &t,int n)
{
Swn<T> result(s);
for (int i=0; i<n; i++) result = df(result,t);
return result;
}
// частный интегргш s по t
template <class Т> Sum<T> Int(const Sum<T> &s, const Sum<T> &T)|
Sum<T> result(1,0);
Symbol<T> *tt;
((Sum<T>&)s) .ShrinkO ;
if (t.type() == JBJ) tt = t.ep[0];
elsett = (Symbol<T>*)&t;
if (tt->type()==S && tt->branches==l && tt->fac_exp [0] ==T(1)) tt = tt->ep[0];
s.integrate(result.ep,tt);
result.Shrink();
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 (type() == ,S‘ && branches==l && fac_exp[0]==T(1))
{ (*(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;
Symbol<T> *ptr;
if (si.type() ==,B>) return put(*(Sum<T>*)(si.ep[0]),s2);
if (type() ==,B>) ptr = ep[0];
elseptr = (Symbol<T> *)this;
((Sum<T>&)sl) .ShrinkO ;
if (si.type() == S && si.branches == 1)
for (i=0; i<ptr->branches; i++)
flag += ptr->ep[i]->replacel(sl.ep[O],(Symbol<T>*)(&s2)); if (ptr->ep[i]->is_equal(sl.ep[0]))
{ s2.copy(ptr->ep+i); flag++; } }
else // (si.branches == 0)
for (i=0; i<ptr->branches; i++)
flag += ptr->ep[i]->replacel((Symbol<T>*)(&sl), (Symbol<T>*)(&s2));
if (ptr->ep[i]->is_equal((Symbol<T>*)(&sl)))
{ s2.copy(ptr->ep+i); flag++; }
}
Shrink0;
return flag;
}
// возвращаем численное значение переменной template <class T> T Sum<T>::nvalue() const {
return nval();
template <class T> T Sum<T>::nval() const
if (type() == ,B,) return (*(Sum<T>*)ep[0]).nval();
if (type() == S && branches==l && fac_exp[0]==T(1)) return (*(Sum<T>*)ep[0]).nval();
int i;
T s(0);
if (!branches)
if (islower(type())) // это - функция
return ((Function<T>*)data)->f(((Sum<T>*)ep[0])->nval()); else
return ( (Meignitude<T>*)data) ->val ();
else
for (i=0; i<branches; i++)
s += fac_exp[i] * ((Sum<T>*)ep[i])->nval(); return s;
// возвращаем значение выражения
template <class T> Sum<T> Sum<T>::value() const return val();
// изменение используемой переменной
template <class T> void Sum<T>: : cleeir ()
if (ep) { removeB(); 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()
int i, flag;
Shrink();
do{
flag = 0;
for(i=0; i<branches; i++) flag += ep[i]->mxpand(ep+i);
if ( !f lag) f or(i=0; i<branches; i++)f lag += ep[i] ->dxpand(ep-* Shrink();
} while (flag);
} while (gather());
Shrink();
// унарный плюс
template <class T> Sum<T> Sum<T>::operator + () const
{ return *this; }
// унарный минус
template <class T> Sum<T> Sum<T>::operator - () const
{ return *this * T(-1); }
template { return
<class
*this :
T> Sum<T> =*this + £
Sum<T>::operator += "}
(const
Sum<T>
&s)
template { return
<class
*this :
T> Sum<T> =*this - $
Sum<T>::operator -= s; }
(const
Sum<T>
&s)
template { return
<class
*this :
T> Sum<T> =*this * s
Sum<T>::operator *=
3; }
(const
Sum<T>
&s)
template { return
<class
*this :
T> Sum<T> =*this / s
Sum<T>::operator /=
3; }
(const
Sum<T>
&s)
template <class T> Sum<T> operator + (const Sum<T> &sl, const Sum<T> &s2)
Sum<T> r(2,0);
if (si.type() == 'B') sl.ep[0]->copy(r.ep);
elsesl.copy(r.ep);
if (s2.type() == ,B>) elseif (s2.type() == ,B>) elses2.ep[0]->copy(r.ep+1);s2.copy(r.ep+l);s2.ep[0]->copy(r.ep+1);s2.copy(r.ep+l);
if (s2.type() == ,B>) else
if (s2.type() == ,B>) else
s2.ep[0]->copy(r.ep+1);
s2.copy(r.ep+l);
s2.ep[0]->copy(r.ep+1);
s2.copy(r.ep+l);
if (si.type() if (s2.type() return r;if (si.type() if (s2.type() return r;
if (si.type() if (s2.type() return r;
if (si.type() if (s2.type() return r;
== V) r .next_var [0] = 1;
== V) r.next_var[1] = 1;
template <class T> Sum<T> operator - (const Sum<T> &sl, const Sum<T> &s2)
Sum<T> r(2,0);
if (sl.type() == ’B>) else
if (s2.type() == ,B,) else
if (sl.type() == W) if (s2.type() == ?V?) r.fac_exp[l] = T(-l); return r;
sl.ep[0]->copy(r.ep);
.copy(r.ep);
.ep[0]->copy(r.ep+1);
s2.copy(r.ep+1);
r .next_var[0] = 1;
r.next_var[1] = 1;
&sl, &s2)&sl, &s2)
&sl, &s2)
&sl, &s2)
template <class T> Sum<T> operator * (const Sum<T> const Sum<T>
{
Sum<T> r(l,0);
Product<T> *p;
р = new Product<Т>(2) г. ер [0] = р;
if (sl.typeO ==,В,) else
assert(р != NULL);
si.ер[0]->copy(г.ер [0]->ер);
s1.сору(г.ер [0]->ер);
if (s2.type()== else
,В,)s2.ep[0]->copy(r.ер[0]->ep+l);
s2.copy(r.ep [0]->ep+l);
if (sl.typeO == 'V') r. ep [0] ->next_veir [0] = 1;
if (s2.type() == ,V') r.ep[0]->next_var[1] = 1; return r;
}
template <class T> Sum<T> operator / (const Sum<T> &sl, const Sum<T> &s2)
((Sum<T>&)s2) .Shrink。;
if (s2.branches == 1 && s2.ep[0] == s2.zero())
cerr << "Division by zero" << endl;
exit (1);
Sum<T> r(l,0);
Product<T> *p = new Product<T>(2);
r.ep [0] = p;
if (sl.typeO == JBJ) si.ep[0]->copy(r.ep[0]->ep);
elses1.copy(r.ep [0]->ep);
if (s2.type()== else
,B,) s2.ep[0]->copy(r.ep[0]->ep+l);
s2.copy(r.ep [0]->ep+l);
if (si.type()== if (s2.type()==
‘V’)r.ep[0]->next_veir[0] = 1;
W) r. ep [0] ->next_vcir [1] = 1;
r.ep[0]->fac_exp[1] = T(-l); return r;
template <class T> int operator == (const Sum<T> 也si, const Sum<T> &s2)
((Sum<T>&)sl).Shrink();
((Sum<T>&)s2) .ShrinkO ;
return si.is_equal((Symbol<T>*)&s2);
}
template<class T>int operator != (const Swn<T> &sl,const Sum<T> &s2) { return !(sl==s2); }
template<class T>ostream & operator << (ostream &os,const Sum<T> &s)
Symbol<T> *ptr;
((Sum<T>&)s) .ShrinkO;
if (s.typeO ==)B)) ptr 二 s.ep[0];
elseptr = (Symbol<T>*)&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->type() == 'd')|| (((Sum<T>*)ptr) ->data->type () == ^O)
os << "; ptr->ep[0]->oprint(os); os << ",";
ptr->ep[l]->oprint(os); os << ”)";
else
{ os << "("; ptr->ep[0]->oprint(os); os << ")”; }
}
else
//не печатаем + , т.к. это - первый член
if(ptr->fас_ехр[0]==Т(-1)) {os << "-и;ptr->ep[0]->oprint(os);} else if (ptr->fас_ехр[0] != T⑴)
{
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(0))
os << "+";
if (ptr->fac_exp[i] == T(l)) ptr->ep[i]->oprint(os);
else {
os << ptr->fac_exp[i];
if (ptr->ep[i] != ptr->one())
{ os <<; ptr->ep[i]->oprint(os); }
} else
{
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> &) { return s; }
// выводим множитель во внешний уровень, возводим в соотв. степень
// для сохранение представления канонической формы
template <class Т> int Sum<T>::shrink_l()
int i, flag=0, k;
Sjnnbol<T> *ptr;
for (i=0; i<branches; i++)
if (ep[i]->type() == 'P')
if (ep[i]->branches == 1 && ep[i]->fac_exp[0] == T(D)
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++)
{
if (ep[i]->ep[k]->type() == 'S, && ep[i]->ep[k]->branches == 1 && ep[i]->ep[k]->fac.exp[0] != T(l))
flag++;
// выводим множитель во внешний уровень fac_exp[i]*=power(ep[i]->ep[к]->fac_exp[0], ep[i]->fac_exp[k]);
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 Т> int Sum<T>::shrink_2() {
int i, k, j, n, flag=0;
Symbol<T> **ept;
T *fac_expt;
int *next_vart;
for (i=0; i<branches; i++)
if (ep[i]->type() == f)
n = branches - 1 + ep[i]->branches;
ept = new Sjnnbol<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++)
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(0))
ep[i] ->deleteAHNodes ();
ep[i] = zero(); fac_exp[i] = T(l);
next.var[i] = 1; flag++;
}
if (ep[i] == zero())
if (branches > 1)
{ removed) ; f lag++; }
else if (branches == 1 && fac_exp[i] !== T(l))
{ fac_exp[i] = T(l); flag++; } }
for (i=0; Kbranches; i++) flag += ep[i]->shrink_3(ep+i); return flag;
}
// суммируем Numbers (thii=W) из Sum template <class T> int Sum<T>::shrink_4()
{
int i, k, flag=0;
for (i=0; Kbranches; i++) flag += ep[i]->shrink_4();
// находим первый узел типа }№
for (i=0; i<branches; i++)
if (ep[i]->type() == 'N') break;
// проверяем остальные узлы; если есть узлы типа ?№ ,
// добавляем к первому узлу
for (k=i+l; k<branches; k++)
if (ep[k]->type() ==)
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; Kbranches; i++) flag += ep[i]->shrink_5(); return flag;
// закрытые методы-функции
template <class T> void Sum<T>::diffRules(Symbol<T>**s, char t)const switch (t)
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>(1w*,char()); assert((*s) != NULL); ep[0]->copy((*s)->ep);
break;
case
JwJ: // (d/dx)cosh(u) = sinh(u) (du/dx)
(*s) = new Sum<T>(}j л,char()); assert((*s) != NULL); ep[0]->copy((*s)->ep);
break;
case
JzJ: // (d/dx)sin(u) = cos(u) (du/dx)
(*s) = new Sum<T>(}c?,char()); assert((*s) != NULL);
ep[0]->copy((*s)->ep);
break;
case
七,;// (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(-l);
ep[0]->copy((*s)->ep[0]->ep);
break;
case
// (d/dx)ln(u) = l/(u) (du/dx)
(*s) = new Sum<T>(l,0); assert((*s) != NULL);
(*s)->ep[0] = new Product<T>(1);
assert((*s)->ep[0] != NULL);
(*s)->ep[0]->fac_exp [0] = T(-l);
ep[0]->copy((*s)->ep[0]->ep);
break;
case
,q,: // (d/dx)sqrt(u) = l/(2*sqrt(u)) (du/dx)
(*s) = new Sum<T>(l,0); assert((*s) != NULL);
(*s)->ep[0] = new Product<T>(1);
assert((*s)->ep[0] != NULL);
(*s)->ep[0]->ep[0] н new Sum<T>(l,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(-l);
(*s)->ep[0]->ep [0]->fac_exp[0] = T(2);
ep[0] ->copy((*s)->ep[0]->ep [0]->ep[0]->ep);
break;
default :; }
template <class Т> void Sum<T>::IntRules(Symbol<T> **s,char t) const
switch (t)
case
,e> : // Int(exp(u),y) = exp(u) copy(s);
break;
case
?j 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 J,char()); assert((*s) != NULL);
ep[0]->copy((*s)->ep);
break;
case
,才:// Int(sin(u) ,u) = -cos(u)
(*s) = new Sum<T>(1,0);assert((*s) != NULL);
(*s)->ep[0] = new Sum<T>(}c*,char());
(*s)->fac_exp[0] = T(-l);
ep[0]->copy((*s)->ep[0]->ep);
break;
case
七,:// Int(cos(u) ,u) = sin(u)
(*s) = new Sum<T>(*zJ,char()); assert((*s) != NULL);
ep[0]->copy((*s)->ep);
break;
case
I: // Int (In (u) ,u) = u*ln(u) -u
(*s) = new Sum<T>(2,0);assert((*s) != NULL);
(*s)->ep[0] = new Product<T>(2):
assert ( (*s) ->ep [0] !== NULL);
(*s)->ep[0]->ep[l] = new Sum<T>(?1?,char());
assert((*s)->ep[0] != NULL);
(*s)->fac.exp[1] = 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) = (2/3)*u*(3/2) = (2/3)*(sqrt(u))"3 (*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(2)/T(3);
(*s)->ep[0]->fac_exp [0] = T(3)/T(2);
ep[0]->copy((*s)->ep[0]->ep);
break;
default :;
}
// определение класса Product
// оператор * по умолчанию коммутативен
template <class Т> int Product<Т>: :Conun = 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_nx) :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 Т> Sum<T> Product<Т>::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 Т> Т 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);
flag++;
else
for (i=0; i<branches; i++) if (ep[i]->is_equal(sl)) { s2->copy(ep+i); flag++; }
return flag;
// возвращает индекс равной компоненты
// возвращает (-1) э если тежого компонента не найдено template <class Т> int Product<Т>: : equal_index(Symbol<T> *s)
int i,j,k;
if (Comm) // коммутативна алгебра
if (branches >= s->branches)
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[jJ) && s->fac_exp[i]==fac_exp[j]) { ip[count] = j; ++count; break; }
}
if(count == s->branches)
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]) && s->fac_exp[i]==fac_exp[k]))
break;
if(i == s->branches)
{
for (i—, к—; i>0; i—, к—) remove (к); return к;
return (Т);
// проверка двух выражений на равенство
template <class Т> int Product<T>::is_equal(Symbol<T> *s) const
if (Comm) // для коммутативной алгебры
int i,k;
int *ip; // ip[]используется как индикатор пройденного узла
// узел произведения должен быть равен произведению узлов
if (s->type() != Р) return 0;
// если s эквивалентен ему, возвращаем 1
if (s == (Symbol<T>*)this) return 1;
// т.к. s не эквивалентен ему (кто-то из них есть
// концевой лист), возвращаем 0
if (!s->branches || !branches) return 0;
// если ветви не равны, возвращаем 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; Kbranches; 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;
}“
else // для некоммутативной алгебры
int i;
// узел произведения должен быть равен произведению узлов
if (s->type() != 'Р') return 0;
// если s эквивалентен ему, возвращаем 1
if (s == (Symbol<T>*)this) return 1;
// т.к. s не эквивалентен ему (кто-то из них есть
// концевой лист), возвращаем 0
if (!s->branches I I !branches) return 0;
// если ветви не равны, возвращаем 0
if (branches != s->branches) return 0;
// ветви равны
// проверяем, все ли поддеревья равны
for (i=0; Kbranches; i++)
if (! (ep[i]->is_equal(s->ep[i]))||fac_exp[i]!= s->fac_exp[i]) return 0;
}
return 1;
// Собираем члены, которые имеют ту же самую переменную template <class Т> int Product<T>::gather()
if (Comm) // для коммутативной алгебры
{
int i, j, flag;
flag = 0;
for(i=0; Kbranches; i++) flag += ep[i]->gather();
// сравниваем различные поддеревья; если равны, собираем их for(i=0; i<branches; i++)
for(j=i+l; j<branches; j++)
if (ер[i]~>is~equal(ер[j])) {
flag++;
fac_exp[i] += fac_exp[j];// суммируем множители
// если fac_exp[] == 0, потом прибавим
if (fac_exp[i] == T(0))
if (branches > 2)
{ remove(j) ; removed) ; j=i;}
else // branches==2,
// удаляем член и открываем узел опе()
remove(j); j=i;
ер[0] = one();
fac.exp[0] = T(l);
else { remove(j); j=i; }
else if (!ep[i]->branches && !ep[j]->branches)
if(islower(ep[i]->type())&&ep [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)
if (ep[i]->is_equal(ep[j]))
flag++;
fac_exp[i] += fac_exp[j];// суммируем множители
// если fac_exp[] == , потом добавим
if (fac_exp[i] == T(0))
if (branches > 2)
{ remove(j) ; removed) ; }
else // branches == 2, // удаляем член и открываем узел опе()
remove(j);
ер[0] = one(); assert(ер[0] != NULL);
fac.exp [0] = T(l);
else remove(j);
else if (!ep[i]->branches && !ep[j]->branches)
if(islower(ep[i]->type())&&ep [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; Kbranches; i++) (*sp) ->next_veir [i] = next.veir [i];
for (i=0;Kbranches;i++) ep [i]->copy((*sp)->ep+i); }
// выводим алгебргютеское выражение
template <class T> void Product<T>: :oprint(ostream &os) const
// не выводим * , т.к. зто - первый член ер [0] ->oprint (os);
if (fас_ехр[0] ! = T(l)) os << "(“ << fac.exp[0] << ")";
// выводим остальные члены for(int i=l; Kbranches; i++) { os «
ep[i]->oprint(os);
if (fac_exp[i] != T(l)) os «,…(” « fac_exp[i] « ")"; }
// удаляем только этот узел
template <class T> void Product<T>::delete0neNode()
delete [] ер; delete [] fac.exp; delete [] next var;
// удаляем все узлы, исходящие из этого
template <class Т> void Product<T>::deleteAllNodes()
if (branches)
{
for (int i=0; i<branches; i++)
if ( !next_veir [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_veir);
assert(*f != NULL);
for (i=0; Kbranches; i++)
{
if (ep[i]->type() == ,S, && ep[i]->branches > 1
&& fac_exp[i] > T(l)
&& double(fac_exp[i]) == int(double(fac_exp[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);
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-1,0);
assert(suml != NULL); assert(sum2 != NULL);
// если раскрытое выражение содержит более двух членов,
// тогда группируем выражения в 1 член в порядке
// раскрытия бинома
if (ер[i]->branches > 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++)
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] = Т⑴;
(*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[ij;
// для члена с последним индексом
(*f)*>ер[i]->fас_ехр[lastindex] = T(l);
(*f)->ер[i]->ер [lastindex] = new Product<T>(l);
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];
// все другие члены
for (m=l; m<lastindex; m++)
// коэффициент произвольного члена
(*f)->ер[i]->fас_ехр[m] = bcoeff [m-1];
(*f)->ep [i]->ep [m]二 new Product<T>(2);
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]->deleteAHNodes();
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, && ep[i]->branches > 1
&& fac_exp[i] > T(l)
&& double(fac_exp[i]) == int(double(fac.exp[i]))) {
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);
f or(m=0;m<newbranches;m++)ep[i]->copy((*f)->ep[i]->ep+m);
ep [i] ->deleteAHNodes ();
do{
flagl = 0;
flagl = (*f)->ep[i]->dxpand((*f)->ep+i);
} while (flagl);
} return flag;
// ДИ стри б у ТИВНЫЙ 3ELKOH
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, && ep[i]->branches > 1) break;
// проверяем для fac_exp[]==1 или если мы используем раскрытие
// скобок бинома/мультинома
if (i < branches && fас_ехр[i] == T(l))
flag++;
*f = new Sum<T>(ep[i] ->branch.es,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.vcir [m];
}
//не назначаем fac_exp[], потому что инициализировано
// 1 конструктором
// Он устанавливается в 1, из-за дистрибутивного закона ep[i]->ep[k] -> copy((*f)->ер [k]->ep+i);
(*f )->ер [к] -^ext^var [i] = ep[i]->next_var[k];
for (m=i+l; m<branches; m++)
ер[m]->сору((*f)->ер[к]->ер+т);
(*f)->ер[к]->fас_ехр [т] = fас_ехр[т];
(*f) ->ер [к] ->next_vcir [т] = next.vcir [т];
deleteAHNodes();
return flag;
// дифференцирование
template <class Т>
void Product<T>::diff(Symbol<T> **s,Symbol<T> *t) const
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++)
if (fac_exp[i] == T(l))
{
(*s)->ep[i] = new Product<T>(bremches);
assert((*s)->ep [i] != NULL);
for (j=0; j<i; j++) {
(*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() == W I I
(*s) ->ep [i] ->ep[i] ->type() == J№ )
(*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];
ер[j]->сору((*s)->ep [i]->ep+j);
}
else // fac_exp[i] != T(l)
(*s) ->ep [i] = new Product<T>(newbrcinches);
assert((*s)->ep[i] != NULL);
for (j=0; j<=i; j++) {
(*s)->ep[i] ->f ac_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 <newbrcinches; 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 (uF) = n u" (n-1) 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[0j == t)
if (fac_exp[0] !=T(-1))// Int(x"n,x)=xz'(n+1)/(n+1)
*s = new Sum<T>(l,0);assert(*s != NULL);
(*s)->ep[0] = new Product<T>(1);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(1));
(*s)->ep[0]->next_var[0]=0;
else// Int(х*(-1),х)=1п(х);
*s = new Sum<T>(111,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,bremches))
*s = new Sum<T>(J iJ,char ());assert(*s != NULL);
copy((*s)->ep);
t->copy((*s)->ep+l);
else
{
*s = new Product<T>(2);assert(*s != NULL);
copy((*s)->ep);
t->copy((*s)->ep+l);
else // ветвей>1
int i, j , const cint _f ound=-l;
//выбор константы в соответствии с интегрированием
if(Comm == 1)
for(i=0; (i<brcinches)&&(constcint_found<0) ;i++) {
if ((ep[i]->type()==,S,)I I(ep[i]->type()==,B,)|I
(ep[i]->type()==〉V,))
{
if(!((Sum<T>*)ep[i])->isdepend(*((Sum<T>*)t))) constcint.found=i;
else
{
if (ep[i]->type() == JNJ) constant一found=i; else
if ( ! implicit_depend(ep[i] ->ep,t ,ep[i] ->brcinches + (ep [i] ->braiiches==0))) const cint_found=i;
if (constcint.found<0)
*s = new Sum<T>(7 i*,char());assert(*s != NULL);
copy((*s)->ep); t->copy((*s)->ep+l);
else
*s = new Product<T>(brcinches) ;assert(*s != NULL);
Product<T> *p=new Product<T>(brcinches-l) ; assert (p ! = NULL);
ep[constant一found]->copy((*s)->ep);
for(i=0,j=0;i<branches;i++)
if (i !=constcint_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 fef:
Symbol<T> *ptr;
ptr = ep[i]->ep[0];
ep[i]_>ep[0] = new Sum<T>(2,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()
int i, flag=0;
for (i=0; i<branches; i++)
{
if (ep[i] == one())
{
if (branches > 1)
remove(i); i—;
flag++;
// (brcinches==l) , помещаем 1" (n) = 1
else if (fac_exp[i] != T(l)) fac_exp[i] = T(l);
for (i=0; i<brcinches; i++) flag += ep[i] ->shrink_l();
return flag;
}
// удаляем лишние узлы Product: передвигаем с нижнего уровня
// последовательности уровней узлов Product на верхний уровень
template <class Т> int Product<T>::shrink_2()
int i, j, k, n, flag=0;
Symbol<T> **ept;
T *fac_expt;
int *next_vart;
for (i=0; i<brcinches; i++) {
if (ep[i]->type() == ,P,)
n = branches - 1 + ep [i] ->brcinches;
ept = new Symbol<T>*[n]; assert(ept != NULL);
fajexpt = 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++)
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_var; ep = ept;
fac_exp = fac_expt;
next_var = next_vart;
flag++;
for (i=0; i<branches; 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<brcinches; i++)
if (ep[i] == zero())
{
*f = zero();
deleteAHNodes ();
return 1;
for (i=0; i<branches; i++) flag += ep[i]->shrink_3(ep+i); return flag;
// суммируем Number (тип=J N?) из Sums
template <class T> int Product<T>::shrink_4()
int i, flag=0;
for (i=0; i<branches; i++) flag += ep[i]->shrink_4(); return flag;
// удаляем Sums только из одного слагаемого
template <class T> int Product<T>::shrink_5()
int i, flag=0;
Symbol<T> *ptr;
for (i=0; i<brcinches; i++)
if (ep[i]->type() == f &&
ep[i]->bremches == 1 &&
ep[i] ->fac_exp [0] == T(l))
{
ptr = ep[i]->ep[0];
next_var[i] = ep[i]->next_var[0];
ep[il->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)
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 для обеспечения связи между машинами с разными внутренними форматами данных.
Типичные приложения PVM — это ситуации, когда изучаемые задачи могут быть распараллелены или может быть применен метод «разделяй и властвуй». Например, рассмотрим логистическое отображение
3+1 = 4д(1 - з), i = 0,1,2,...,
с заданным ж0 (начальное значение). Отображение может быть разбито на два отображения, задаваемых соотношениями
3+2 = 16д(1 — g)(l - 4д)(1 - з)),
3+з - 16g+1(1 - g+1)(1 - 4f±+i)(l - £计1)).
【две за- как呱【две за- как呱Два ряда независимы и могут быть вычислены раздельно. Запускаются дачи, чтобы вычислить оба ряда. После этого ряды объединяются. Так 】
【две за- как呱
【две за- как呱
И11аексах),И11аексах),отображения идентичны (за исключением начальных значений в их
И11аексах),
И11аексах),
могут быть порождены два идентичных подчиненных процесса.
Ниже мы показываем, как PVM может быть использована вместе с аб страктными типами данных (ADT). В следующей программе мы вычисляе ” сумму двух рациональных чисел А и В, где
人 41152 d 123234人 D 40600126678
218107’333445 д72726688615,
Программа состоит из двух частей. Взаимодействие основной и подчиненной программ таково:
Основная программа определяет величины / и В, упаковывает нисла и затем посылает их в подчиненной программе по сети. Подчиненная программа производит вычисление / + В 且 возвращает результат вычисления в основную программу.
Подчиненная программа, которая находится на удаленной машине, сначала принимает пакет, посланный основной программой, распаковывает его, выполняет сложение и, наконец, возвращает результат вычисления обратно в основную программу.
Отметим, что библиотечные программы PVM не поддерживают упаковку абстрактных типов данных. Поэтому мы должны иметь наши собственные функции упаковки и распаковки:
pvm_pkvlong() упаковывает данные типа Verylong.
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
int length = strlen(s);
pvm.pkint(felength,1,1);
pvm_pkstr(s);
void pvm_upkvlong(Verylong *1)
int length;
pvm_upkint(felength,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();
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 main()
// Включаем задачу в PVM запрашивая ID задачи int tid = pvm_mytid();
// Открываем два рациональных числа:
Rational<Verylong> А("41152","218107"), B("123234r,,"333445"
// Запускаем подчиненную задачу
int slaveid;
int error = pvm.spawnC'slave'*, (char**)NULL, PvmTaskDefault, "", 1, feslaveid);
if (error > 0) // если error < 0, подчиненная задача // не может быть згшущена
// Отправляем два рациональных числа pvm_init send(PvmDataDef ault);
pvm.pkrat(&А);
pvm_pkrat(&В);
pvm_send(slaveid, 1);
// Получаем ответ pvm_recv(slaveid, 2); Rational<Verylong> С; pvm_upkrat(&С);
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 -> chaG int length = strlen(s);
pvm_pkint(felength,1,1);
pvm_pkstr(s);
void pvm_upkvlong(Verylong *1)
int length;
pvm_upkint(felength,1,1);
char *s = new char[length+1];
pvm_upkstr(s);
Verylong v(s);
1 = v;
// Упаковка и распаковка для Rational void pvm_pkrat(const RationaKVerylong> *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 main()
// Запускаем задачу в PVM запрашивая ID родителя int ptid = pvm_parent();
// Получаем два рациональных числа
Rational<Verylong> А, В;
pvm_recv(ptid,1);
pvm_upkrat(&А); pvm_upkrat(&В);
// Производим вычисления
Rational<Verylong> С = А + В;
// Отправляем ответ обратно
pvm_initsend(PvniDataDef ault);
pvm.pkrat(&С);
pvm_send(ptid, 2);
// Задача успешно завершена pvm_exit();
}
На выходе программы имеем
41152/218107 + 123234/333445 = 40600126678/72726688615.
Отметим, что в функции упаковки pvm_pkvlong() оператор
char *s = (char*)(*1);
преобразовывает число типа Verylong во встроенный тип данных char*. Этот шаг необходим, поскольку PVM поддерживает только упаковку встроенных типов данных. Оператор преобразования был реализован в классе Verylong (см. гл. 6).
Г ла.Ва. 12
Техника о5ра5отки ошибок
1склюнение является ошибкой, которая происходит во время исполнения Гипичным примером является деление на нуль. Поэтому было бы хорошей гдеей разбить программу на отдельные подсистемы, которые или выполняют- я успешно или терпят неудачу. В этом случае локальная проверка ошибок ,программе позволит обеспечить надежность всей системы. Следовательно, ,есьма важно идентифицировать возможные источники ошибок. Они могут ътть вызваны самим программистом. Сюда же относится и обнаружение вну- ренних логических ошибок, таких, как, например, ошибочные утверждения. 1ногда специальное внимание должно быть обращено на условия выполне- [ия функции. Например, попытка получить седьмой символ трехсимвольной троки должна вызывать ошибку. Однако некоторые возникающие проблемы te являются логическими ошибками, а являются отказами при попытке полу- [ить некоторый ресурс во время исполнения. Это такие ошибки, как нехватка [амяти, неудача записи из-за переполнения диска и т. п.
Устойчивая к ошибкам система обычно имеет иерархическую структуру, а каждом уровне которой исправляется максимально возможное количество шибок. Тем не менее некоторые ошибки не могут быть обработаны локально. :аким образом, нам нужен глобальный механизм связи с ошибками, который юг бы производить следующее:
Выдавать состояние ошибки, т. е. возвращать разные значения функции или метода класса для представления различных исходов выполнения функции.
Обрабатывать исключительные ситуации.
;языке С мы можем использовать функцию void assert (int test) для про- ерки условий и возможного аварийного завершения. Эта функция является акросом, который распространяется на оператор if; если test получает зна- ение нуль, то assert выдает сообщение в поток stderr и аварийно завершает рограмму. Используя подсистему обработки исключительных ситуаций языка :++, мы можем структурировать и контролировать способы обработки °皿*~ эк времени исполнения. Обработка исключительной ситуации С十十 строится а трех ключевых словах: try, catch и throw.
Состояние ошибки
Можно реализовать состояния ошибок в виде переменных класса. Естественно, каждый объект имеет флаг ошибки (переменная статуса ошибки). Если ошибка происходит, то переменная статуса ошибки устанавливается в значение, указывающее тип ошибки. Например, библиотека <iostream.h> C++ имеет целочисленную переменную статуса state. Разные биты этой переменной указывают разные статусы ошибки. Статус ошибки может быть опрошен через методы класса bad(), eof (), fail(). На их основе пользователь может затем реализовать свой собственный механизм обработки ошибок.
Обработка исключительной ситуации
Более совершенный метод управления обнаружением и обработкой ошибок состоит в обработке исключительной ситуации. Это — нелокальный механизм, который обеспечивает средства передачи ошибок между классами/функциями и программами, которые используют эти классы/функции. Он также позволяет программистам писать свой код, не беспокоясь об ошибках при каждом вызове функции.
В механизме обработки исключительной ситуации в C++ функция, которая обнаруживает появление ошибки, порождает исключение, используя ключевое слово throw. Функция-обработчик исключения затем перехватывает и обрабатывает это исключение. Функция, которая порождает исключение, должна находиться внутри try-блока. Обработчики, которые объявляются с использованием ключевого слова catch, располагаются после try-блока.
В качестве примера рассмотрим следующий класс Rational вместе с программой main ():
// except.срр
#include <iostream.h>
#include "MString.h"
class Rational
private:
long int num, den;
public:
Rational(const long int = 0, const long int = 1);
};
class Check_Error
{
private:
String reason;
public:
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()
long int a, b;
a = 5; b = 0;
Rational RI(a,b);
cat ch(Check_Error diag)
cerr « "Внутренняя ошибка: " « diag. diagnostic () << endl; return 1;
}
return 0;
}
езультат
нутренняя ошибка: Деление на нуль !
Конструктор класса Rational проверяет, является ли знаменатель числа улем. Он создает исключительную ситуацию при появлении этой ошибки, озданный функцией, инициирующей исключение, объект в этом случае со- эржит диагностическое сообщение
Деление на нуль!
ласс Check_Error обрабатывает все исключения, которые могли произойти пределах программы. Он содержит в области private (закрытые данные) временную, которая хранит строку, содержащую причину исключения. Метод LagnosticO печатает диагностическое сообщение об аварии. Он используется функции catch программы.
Отметим, что в функции main() вся программа располагается в 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(х)
чертит простую кривую косинуса, и
gnuplot> splot х*х*у
чертит трехмерную поверхность. Чтобы начертить файл данных с именем result. dat, мы набираем
gnuplot> plot ?result.txtJ
Gnuplot имеет интерактивную помощь, которая может выводиться командой help со следующим синтаксисом:
gnuplot> help < topic >
где <topic> ссылается на конкретную тему в контексте. По поводу других команд gnuplot мы отсылаем к руководству пользователя [59].
PostScript был создан и развивается в Adobe Systems Incorporated. В основном это язык описания страниц, который разработан специально для
того, чтобы обеспечить аппаратно-независимое описание выводимого 讨飞 т
“ском
пьютерном системы документа на принтер. Он является также языком гг граммирования общего назначении, который содержит большое разнообраз°~ графических операций. Он поддерживает типы данных, такие, как вехцестве"。 ные числа, логический, массивы и строки. Кроме того, он содержит перемен" ные и допускает построение более сложных процедур и функций, комбинип ' различными операторами.
PostScript включен в программу интерпретатора, которая обычно запус кается на независимом устройстве, например лазерном принтере. Программа интерпретатор а переводит операции PostScript и данные в коды устройства и генерирует графику, рисуемую на листе. Одна из сильных сторон PostScript — это то, что он не зависит от устройства вывода, будь то наборщик или издательское устройство высокой плотности печати.
Файл PostScript должен начинаться с символа %!. Это магическое число является признаком PostScript файла. PostScript является постфиксным языком, где операторы следуют за своими аргументам. Например, оператор
n у moveto
перемещает текущий указатель в координаты (ж, у). В общих чертах синтаксис данной команды таков:
argi arg2argi arg2argnn operatorargnn operator
argi arg2
argi arg2
argnn operator
argnn operator
%!PostScript70 140 moveto300 620 lineto630 140 lineto closepath stroke/Palatino-Roman findfont 65 scalefont setfont 140 148 movato 50 rotate(SymbolicC++) show showpage%!PostScript70 140 moveto300 620 lineto630 140 lineto closepath stroke/Palatino-Roman findfont 65 scalefont setfont 140 148 movato 50 rotate(SymbolicC++) show showpage
%!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
%!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
Следующая программа PostScript рисует треугольник и размещает текстовое сообщение "SymbbolicC++,1 под углом 50° к горизонтали.
% "Магическое число" PostScipfa
% установка начальной точки
% добавить отрезок
% добавить отрезок
% замкнуть фигуру
% нарисовать траекторию
% выбрать требуемый шрифт
% установка размера шрифта
% выбрать текущий шрифт
% установить текущую точку
% поворот системы координат
% указать текст сообщения
% вывести лист на экран
Другие команды языка PostScript можно найти в [21], [33]. Предположим теперь, что мы хотим наглядно изобразить эволюцию во времени логистиче ского отображения
= 4g(1 — 3),力=0,1,2, •…,д € [0,1]
с ①。=1/3. Для начала мы сгенерируем набор данных (в файле timeev.dat)J используя следующую программу:
// logis.cpp
#include <fstream.h> // для ofstream, close()
#include "Verylong.h"
#include "Rational.h*1
void main() {
of stream data("timeev. daf);
Rational<Verylong> ci("i”), c2("4”), x0(”i",”3"); // начальное значение xO = 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.close();
Эволюция во времени отображения timeev. dat теперь может быть изображена с помощью Gnuplot. В среде Gnuplot мы набираем следующую команду:
gnuplot> plot [0:10] 7timeev.dat?
Эта команда вычерчивает первые одиннадцать точек эволюции во времени.
Мы можем создать постскриптовский файл, используя команды:
gnoplot> set term postscript default gnuplot> set output "timeev.ps" gnuplot> plot Jtimeev.datJ
Постскриптовский файл timeev.ps можно послать на принтер, используя команду:
copy timeev.ps Ipt1
в средах MS-DOS, OS/2, Windows NT и
Ip timeev.ps для систем Unix.
Список литературы
1.2.3.1.2.3.Ammeraal been, STL for C++ Programmers^ John Wiley, Chichester (1997).
1.
2.
3.
1.
2.
3.
Anderson J. R., Corbett A. T. and Reiser B. J., Essential LISP、Addison-Wesley (1QR7\ Aslaksen H., Multiple-valued Complex Functions and Computer Algebra SICS A о Bull., 30, 12-20 (1996). 5 M
Ayres F., Modem Algebra, Schaum's Outline Series, McGraw-Hill, New York (1965)
Barnsley M. F., Fractals Everywhere, 2nd ed., Academic Press Professional Boston (1993).5
Berry J. T.} The Waite Group7s C++ Programming^ 2nd ed., SAMS, Carmel Indiana (1992).,
Birtwistle G.M., Dahl O. J.} Myhrhaug B. and Nygaard K., SIMULA BEGIN, Stu- dentlitteratur, Sweden, Auerbach, Philadelphia (1973).
Brackx F., Computer Algebra with LISP and REDUCE: An Introduction to Computer- aided Pure Mathematics, Kluwer Academic, Boston (1991).
Budd T. A., Classic Data Structures in C++, Addison-Wesley (1994).
Char B. W.} First Leaves — A Tutorial Introduction to MAPLE V, Springer-Verlag New York (1991).'
Dautcourt G., Jann К. P., Riemer E. and Riemer M., Astronomische Nachrichten, 102 1 (1981).'
[12] Davenport J. H., Siret Y. and Tournier E., Computer Algebra: Systems and Algorithms for Algebraic Computation^ 2 ed., Academic Press, London (1993). [Имеется перевод с 1-го издания: Давенпорт Дж., Сирэ Й., Турнье Э. Компьютерная алгебра.—М.: Мир, 1991.]
Ellis М. A. and Stroustrup В., The Annotated C++ Reference Manual, Addison- Wesley. (1990). [Имеется перевод: Эллис M. Строуструп Б. Справочное руководство по языку программирования C++ с комментариями. — М.: Мир, 1992.]
Epstein R. L. and Carnielli W. A., Computability^ Wadsworth & Brooks/Cole, Pacific Grove, California (1989).
Froberg С. E., Numerical Mathematics, Theory and Computer Applications, Benjamin- Cummings, Menlo Park (1985).
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 (1994).
Gray P.D. and Mohamed R., Smalltalk-80: A Practical Introduction, Pitman, London (1990).
L8. Hearn A., REDUCE Userys Manual^ Version 3.5, RAND publication CP78 (Rev. 10/93).
L9. Hehl F. W., Winkelmann V. and Meyer H.; REDUCE, 2 ed., Springer-Verlag (1993).
20. Hekmatpour S., An Introduction to LISP and Symbol Manipulation^ Prentice Hall, New York (1988).
!1. Holzgang D. A., Understanding PostScript Programming^ second edition, SYBEX Inu, Alameda (1987)..
!2. Howson A. G-, A Handbook of Terms used in Algebra and Analysis^ Cambridge Um~ versity Press, Cambridge (1972).
!3. Jamsa K., Success with C++, Jamsa Press, Las Vegas (1994). [Имеется русский перевод: Джамса К. Учимся программировать на языке C++. — М.: Мир, 1997, 19"' 2001.]
Jenks R. D. and Sutor R. S., Axiom: The Scientific Computation System、Springer- Verlag, New York (1992).
Kim W., Introduction to Object-Oriented Databases, MIT Press, Cambridge, MA (1990).
Knuth D. E., Fundamental Algorithms^ vol. 1 of The Art of Computer Programming^ Addison-Wesley, Reading, MA (1968). [Имеется перевод: Кнут Д., Искусство программирования для ЭВМ: в 3 т.; т. 1: Основные алгоритмы. — М.: Мир, 1976.]
Knuth D. Е., Seminumerical Algorithms^ 2nd ed., vol. 2 of The Art of Computer Programming, Addison-Wesley, Reading, MA (1981). [Имеется перевод: см. [26]; т. 2: Получисленные алгоритмы. — М.: Мир, 1977; М.: «Вильямс», 2000.]
Knuth D. Е., Sorting and Searching^ vol. 3 of The Art of Computer Programming^ Addison-Wesley, Reading, MA (1973). [Имеется перевод: см. [26]; т.3: Сортировка и поиск. — М.: Мир, 1978; М.: «Вильямс», 2000.]
Lafore R., Object-Oriented Programming in TURBO C++、Waite Group Press, Mill Valley (1991).
Lang S., Linear Algebra, Addison-Wesley, Reading, MA (1968).
Lippman S.B., C++ Primer^ 2nd ed., Addison-Wesley, Reading, MA (1991).
McCarthy J. et al., LISP 1.5 Programmer^ Manual^ 2 ed.} MIT Press (1965).
McGilton H. and Campione M., PostScript by Example, Addison-Wesley (1992).
MacCallum M. A. H. and Wright F. J., Algebraic Computing with Reduce^ Clarendon Press, Oxford (1991).
Mevel A. and Gueguen T., Smalltalk-80, Macmillan Education, London (1987).
Meyer B.} Object-Oriented Software Construction^ Prentice-Hall International (1988).
Meyers S., Effective C++、Addison-Wesley, Reading, MA (1992).
Norvig P., Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp, Morgan Kaufman, San Mateo (1991).
Oevel W., Postel F., Riischer G., and Wehmeier S., Das MuPAD Tutorium^ SciFace Software, Paderborn (1998).
Press W. H., Teukolsky S. A., Vetterling W. T. and Flannery В. P., Numerical Recipes in C, The Art of Scientific Computing, Cambridge University Press (1992).
Rayna G., REDUCE: Software for Algebraic Computation^ Springer-Verlag, New York (1987).
Reiser M., The Oberon System: user guide and programmer^ manual, Addison-Wesley (1991).
Reiser M. and Wirth N.3 Programming in Oberon, Addison-Wesley (1992).
RischR. H., Transactions о/ the American Mathematical Society, 139, 167-189 (1969).
Smith M. A., Object-Oriented Software in C++, Chapman and Hall, London (1993).
Steeb W. H. and Euler N., Nonlinear Evolution Equations and Painleve Test, World Scientific, Singapore (1988).
Steeb W. H. and Lewien D., Algorithms and Computation with ReduceBl-Wissen- schaftsverlag, Mannheim (1992).
Steeb W. H., Lewien D. and Boine-Frankenhein O.)Object- Oriented Programming in Science with C++)Bl-Wissenschaftsverlag, Mannheim (1993).
Steeb W. H., Quantum Mechanics using Computer Algebra^ World Scientific (1994).
Steeb W. H. and Euler N., Continuous Symmetries, Lie Algebras and Differential Equations, World-Scientific, Singapore (1996).
Steeb W. H., Problems and Solutions in Theoretical and Mathematical Physics, World Scientific, Singapore (1996).
Steeb W. H., The Nonlinear Workbook: Chaos, Fractals, Cellular Automata^ Neural Networks, Genetic Algorithms, Fussy Logic with C++, Java, SymbolicC-F-f- and Reduce Programs, World Scientific, Singapore (1999).
Stephani H., General Relativity, Cambridge University Press, Cambridge (1985).
Stoutemeyer D.R., Notices of the American Mathematical Society, Volume 38, Stro^trup5B^9^e C++ Programming Language, 2nd ed., Addison-Wesley, Reading, T^nn^l^K. D., Principles of Programming Languages, Prentice-Hall, Englewood Cliffs
Thomas P. and Weedon R・,Object-Oriented Programming in Eiffel, Addison-Wesley, Tb^iky^.^S^omwon Lisp: A Gentle Introduction to Symbolic Computation, Beniamin-Cummings, Redwood City (1990).mD
Williams T., Kelley C. et al., Gnuplo: An Interactive Plotting Program, http://www.cs.dartmouth.edu/gnuplot-infо.html.
Winston P.H., Lisp, Addison-Wesley (1989)... c +」
'wirth N. and Gutknecht J., Project Oberon: The Design of an Operating System and
Compilers, Addison-Wesley (1992).「 j. c力
WolKm S,5 Mathematica: A System for Doing Mathematics by Computer, 2nd ed.,
§::g°3・甯:終\。J.S. and MacGergor T., Ripple Effect Analysis of Software Main- 'knance:% Proc. COMPSAC78, pp. 60-65 (1978).
Предметный указатель
car
384
cdr
384
cond
386
Eiffel
107
Java
89
map
179
mapcan
386
null
385
private
127
protected
127
public
127
Smalltalk
110
this 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
Антикоммутационное соотношение (anticommutation relation) 144
Аппроксимация Паде (Fade approxi- mant) 361
Аргумент (argument) 31
Арифметизация (arithmetization) 302
Атом (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 subspace) 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
Цифференциал (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) 35
произведение (product) 54, 223
Куча (heap) 119
Лестница дьявола (devil's staircase) 317
Линейная независимость (linearly inje_ pendent) 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) ИЗ
Ложное решение (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^) 57
логарифмических слагаемых (logarithmic part) 50
наивный (naive) 48
Ньютона—Рафсона (Newton—Ra,p^_
son scheme) 379
Пикара (Picard's) 72, 366
Эрмита (Hermite3s) 48
Метрика Хаусдорфа (Hausdorff metric) 315
Мнимая часть (imaginary part) 30
Многочлен (polynomial) 39
Лагерра (Laguerre) 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
expansion)expansion)Свободная переменная39Свободная переменная39(indeterminate)(indeterminate)
expansion)
expansion)
Свободная переменная39
Свободная переменная39
(indeterminate)
(indeterminate)
метрического тензора (metric tensor) 62
метрическое тензорное поле Гёделя (Goedel metric tensor) 355
шорядоченное (ordered) 28
лиморфизм (polymorphism) 83
лучатель (receiver) 79
лярные координаты (polax coordinates) 31
следовательность Коши (Cauchy sequence) 311
стфиксный язык (postfix language) 610'
авило дифференцирования (differentiation rule) 214
дифференцирования произведения (product) 269
Лейбница (Leibniz) 323
Попиталя (L^ospitaFs) 13
юрождающие правила (production rules) 259
сложения (интегралов) (sum) 47
суммирования (sum convention) 63 едставление Лакса (Lax representa
tion) 358
ефиксная нотация (prefix notation) 388
оизведение векторное (cross product) 33, 219
щакопеременное (alternating) 55
:калярное (scalar) 219
оизводнал (derivative) 46
Чи (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 (Toda lattice) 358
Решето Эратосфена (sieve of Eratos thenes) 24
Ряд кумулянтов (cumulant 336
Самоименующий указатель (self-referential pointer) 123
Сверхдлинные целые числа (very long 亩_ tegers) 194
Свойство архимедово (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
Тензор кривизны (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
Класс Rational 426
Класс Complex 431
Класс Quaternion 436
Класс Derive 440
Класс Vector 442
Класс Matrix 452
Класс Array 465
Класс String 483
Класс BitVec 487
Класс MList 492
Класс Polynomial 499
Класс Set 523
Класс Symbolic 529
ава 11. Параллельная виртуальная машина и абстрактные типы данных 601
ава 12. Техника обработки ошибок 606
Состояние ошибки 607
Обработка исключительной ситуации 607
ава 13. Программа Gnuplot и язык PostScript 609
исок литературы 612
юдметный указатель 615
Учебное издание
Киат Ши Тан, Вилли-Ханс Стиб, Йорик Харди
СИМВОЛЬНЫЙ C++:
Введение в компьютерную алгебру с использованиемобъектно-ориентированного программирования
Зав. редакцией академик В. И. Арнольд
Зам. зав. редакцией А. С. Попов
Ведущий редактор Г. М. Кобельков
Художник Э. А. Смирнов
Художественный редактор Н. В. ЗотоваТехнический редактор О. Г. ЛапкоОригинал-макет подготовлен С. А. Янковой в пакете 倬I且Х2ес использованием кириллических шрифтов семейства 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 л., ил.
Книга известного американского математика- вычислителя представляет собой учебник повышенного уровня по вычислительным методам линейной алгебры, выделяющийся среди изданий этого типа рядом особенностей:
знакомит с современными методами решения линейных систем, задач наименьших квадратов, вычисления собственных значений и сингулярных разложений;
прививает читателям навыки эффективного решения реальных задач путем выбора наилучших алгоритмов;
содержит упражнения и задачи, облегчающие усвоение материала;
изложение сопровождается многочисленными ссылками на Интернет-ресурсы по реализации конкретных алгоритмов;
материал книги самодостаточен, от читателя требуется только знакомство с основами линейной алгебры.
Основное содержание книги:
Введение
Решение линейных уравнений
Линейные задачи наименьших квадратов
Несимметричные задачи на собственные значения
Симметричные задачи и сингулярное разложение
Итерационные методы для линейных систем
Итерационные методы для задач на собственные значения
2 令2 令ns・*sur.mu@JOA-A--sm—wAv s'寸 8,983-60)зхеф 0's'98z 仆 E.8—98Z 488fE98ZG60)=qHoeaIfaL фз—rtlw 7 Tdgu «эо^э^,^soos CIIOJ C966z3ad!7vns・*sur.mu@JOA-A--sm—wAv s'寸 8,983-60)зхеф 0's'98z 仆 E.8—98Z 488fE98ZG60)=qHoeaIfaL фз—rtlw 7 Tdgu «эо^э^,^soos CIIOJ C966z3ad!7vДля студентов и аспирантов вузов и университетов, изучающих вычислительную математику и ее приложения.
2 令
2 令
ns・*sur.mu@JOA-A--sm—wAv s'寸 8,983-60)зхеф 0's'98z 仆 E.8—98Z 488fE98ZG60)=qHoeaIfaL фз—rtlw 7 Tdgu «эо^э^,^soos CIIOJ C966z3ad!7v
ns・*sur.mu@JOA-A--sm—wAv s'寸 8,983-60)зхеф 0's'98z 仆 E.8—98Z 488fE98ZG60)=qHoeaIfaL фз—rtlw 7 Tdgu «эо^э^,^soos CIIOJ C966z3ad!7v
К. LU. Тан, В.-Х. Стиб, Й. Харди
Символьный C++
введениев компьютерную алгебрус использованиемобъектно-ориентированногопрограммирования
* Краткое введение в язык C++ и объектно- ориентированное программирование, на основе которых построена система компьютерной алгебры Symbolic C++.
* Обзор наиболее известных систем (REDUCE, MAPLE, AXIOM, MATHEMATICA, MuPAD).
* Примеры применения к различным задачам математики и физики.
* Обсуждение объектно-ориентированных языков программирования (Java, Eiffel, Smalltalk, Оберон).
* Книга идеально подходит для использования в курсе по символьным вычислениям и объектно-ориентированному программированию.
• Мы должны достаточно легко создавать новые экземпляры объектов класса Verylong.
• Мы должны уметь совершать над элементами класса Verylong арифметические операции, такие, как +, -, *, /, %, ++,--.
• Мы должны уметь присваивать значения чисел из Verylong переменным типа Verylong, используя оператор =.
• Должны поддерживаться модификации оператора присваивания, такие, как +=,-=,*=,/=, %=•
• Должны быть доступны операторы сравнения >, >=, <, <=, ==, !=•
• Мы должны уметь, когда это нужно, преобразовывать числа из Verylong » формат данных других стандартных типов, например int, double, cha^-
• Класс реализован как шаблонный класс, поскольку шаблоны работают очень хорошо в контейнерных классах.
• Конструкция Array проста.
• Доступны простые арифметические операторы типа +, -, *.
• Перегружены операторы присваивалия = и присваивания с изменением +=, -=, *=.
• Мы можем копировать один массив в другой, используя оператор присваивания =.
* Оператор взятия нижнего индекса [] перегружен. Он обеспечивает доступ к индивидуальному элементу и возвращает ссылку на этот элемент.
* Операторы равенства (==) и неравенства (! = ) проверяют, содержат ли два
массива те же самые элементы и в том же самом порядке.
Д"я массива размерности N метод size(int index) возвращает число элементов в index-м измерении массива, где index= 0,..., N — 1. Например,- size(O) возвращает число элементов по нулевому измерению для одно-, 期У・,трех- и четырехмерных массивов.
resize () переопределяет размер массива к специфицируемому размеру. П°Дд;ерживаются операции потокового ввода и вывода в классе Array.
К. Ш. Тан, В.-Х. Стиб, Й. ХардиСимвольный C++введениев компьютерную алгеВрус использованиемобъектно-ориентированногопрограммированияC++
Tan Kiat Shi, Willi-Hans Steeb and Yorick Hardy
Symbolic C++:An Introduction toComputer Algebra usingObject-Oriented Programming
2nd extended and revised edition
竇 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 pTogrammhng by Kiat Shi Tan, . Willi-Hans Steeb, Yorick Hardy
Copyright © Springer-Verlag
London Limited, 1998, 2000
ISBN 5-03-003355-6 (русск.)ISBN 1-85233-260-3 (англ.)ISBN 5-03-003355-6 (русск.)ISBN 1-85233-260-3 (англ.)All Rights Reserved
ISBN 5-03-003355-6 (русск.)
ISBN 1-85233-260-3 (англ.)
ISBN 5-03-003355-6 (русск.)
ISBN 1-85233-260-3 (англ.)
© перевод на русский язык,оформление,《Мир», 2001