Программирование в современных средах

advertisement
1
Программирование в современных средах
В первой части курса обсуждаются особенности языков программирования
Delphi, C++ и C# , их отличия и сходство. Во второй части рассматриваются
примеры конкретных классов, написанных на разных языках, и приложений
разного типа, созданных в различных средах. В этой части продолжается
обсуждение особенностей всех трех языков, и сравниваются технологии
использования сред CodeGear фирмы Borland и Microsoft Visual Studio для
создания консольных и оконных приложений.
Предполагается, что слушатель освоил курс объектно-ориентированного
программирования и имеет на своем компьютере среду CodeGear 2007 фирмы
Borland (либо свободно распространяемые версии Turbo Delphi Explorer и Turbo
C++ Explorer) и среду MS Visual Studio 2005, либо 2008.
Основным источником информации по языкам и по конкретным операциям в
среде служит справочная система, устанавливаемая вместе с IDE среды.
Введение
Современное программирование представляет собой процесс алгоритмизации
вычислений посредством создания и использования специальных типов
переменных – классов. Классы являются основными носителями кода, который
используется приложением или программой. Приложение содержит код,
создающий объекты – переменные, или экземпляры описанных типов-классов,
и использует методы этих объектов, либо методов самих классов. Код
приложения, в свою очередь, создается и отлаживается с помощью сред
программирования. Среды не только облегчают техническую сторону создания
программы, предоставляя для этого графический интерфейс и инструменты
отладки, но и содержат довольно обширные библиотеки классов, часто
используемых при создании разного типа приложений. При создании
приложений в среде фирмы Borland будет использоваться так называемая VCL
– Visual Component Library (библиотека визуальных компонент). Приложения в
среде фирмы Borland будут создаваться для работы на традиционной платформе
Win32. В среде Microsoft Visual Studio будут использоваться классы библиотеки
.NET и приложения будут предназначены для работы на современной
платформе .NET Framework фирмы Microsoft.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
2
Языки программирования (сравнительный обзор)
В этой части курса дается краткое описание основных понятий
программирования в приложении к языкам Delphi (Object Pascal), C++ и C#.
Числа и идентификаторы
Информация в компьютере представлена в форме чисел. Есть числа-адреса,
которые адресуют ячейки памяти компьютера. Переменные, хранящие адреса,
часто называют указателями. В некоторых случаях адресами можно
манипулировать как обычными числами, в других адреса представляют собой
лишь ссылки на ту или иную область памяти, и изменять их нельзя. Другой,
более распространенный, тип чисел отображает данные для арифметических
или других действий, зависящих от типа данных. В частности, указатели так же
являются одним из типов данных, но ссылки, вообще говоря, нет. Наконец, есть
числа, кодирующие команды, выполняемые процессором компьютера.
Программист практически никогда не имеет дела с этими последними числами,
называемыми кодами, в их непосредственном выражении. В коды
превращаются те команды (операторы), которые пишет программист при
работе с данными, используя синтаксические правила языка программирования.
Обычно данные обозначаются сочетанием букв и цифр, именуемых
идентификаторами. Идентификаторы могут состоять только из букв, цифр и
знака подчеркивания, но не могут начинаться с цифры. В современных языках
программирования,
использующих
так
называемый
универсальный
(двухбайтовый) код (или Unicode) при записи идентификаторов программы, в
обозначениях идентификаторов могут использоваться буквы кириллицы.
Транслятор Delphi не отличает в идентификаторах большие и малые буквы
(идентификаторы aBBa и AbBa воспринимаются как одинаковые), а в языках C
это разные символы.
Числа в программировании могут использоваться и непосредственно в форме
числовых постоянных. Числовые постоянные (numerals) могут иметь разный
формат записи в зависимости от языка, но практически везде для записи целых
десятичных постоянных используется сочетание цифр со знаком или без него,
как 234 или -10009, а для записи вещественных чисел (чисел с плавающей
запятой) форматы вида -1.234, или 2.03e-13. Знак e (экспонента) используется
для указания степени десяти. Поэтому последнее число читается как 2,03*10-13.
Вместо знака e можно писать E. В языках C можно написать .1 для изображения
одной десятой, но не в Delphi. Запись числа в форме 2. эквивалентна записи
вещественного числа 2.0.
Иногда для записи целых нумералов используется 16-ричная система
исчисления. Так число 256 записывается в 16-ричной системе в Delphi как $FF, а
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
3
в C++ и C# как 0xff, или 0Xff. Символы a, b, c, d, e, f (малые или большие)
отвечают 16-ричным числам от 10 до 15.
Идентификаторы используются не только для обозначения данных, но и для
обозначения методов (процедур, функций), пользовательских типов данных,
имен программных модулей и, так называемых, пространств имен в языках C.
Типы данных
Все данные подразделяются на типы. Это могут быть простые типы данных,
содержащие целые или вещественные числа, булевские постоянные
«правда/ложь», отдельные символы. Структурные типы данных более
сложные – массивы данных, структуры (или записи), файлы, наконец, классы и
объекты.
Простые типы
Переменная, или экземпляр каждого типа данных занимает определенное
фиксированное этим типом число байтов в памяти. Байтом называется
минимальная адресуемая область памяти; в байт можно поместить один символ
ASCII с расширением, целое положительное число от 0 до 255 или целое число
со знаком от отрицательного числа -128 до положительного числа 127. Простые
типы данных занимают сравнительно небольшое число байтов.
Существует несколько типов целых чисел. Эти типы зависят от числа
занимаемых байтов и от наличия знака. Обычно в языке представлены целые
типы, занимающие 1, 2, 4 и 8 байт памяти. В Delphi 1 байт занимают типы
Shortint (со знаком) и byte (без знака), 2 байта – типы Smallint (со знаком) и
Word (без знака), 4 байта – типы integer, или Longint (со знаком) и Cardinal, или
Longword (без знака). Целый 8-байтовый тип со знаком обозначается в Delphi
идентификатором Int64. Аналогичные типы в C++ Borland обозначаются
соответственно однобайтовые (signed или unsigned) char, 2-байтовые (signed
или unsigned) short, 4-байтовые (signed или unsigned) int и 8-байтовые
(signed или unsigned) long. В C# подобные типы обозначаются sbyte, byte;
short, ushort; int, uint и long, ulong. В языках C тип символьный тип так же
относится к целочисленным типам. Это явно видно из перечисления,
сделанного выше для C++. В Delphi существуют два символьных типа. Один
тип это char (или AnsiChar) обычный однобайтовый символ, второй тип
WideChar – тип символа универсальной кодировки (Unicode), который занимает
два или более байтов. В языке C# тип char является символом универсальной
(2-байтовой) кодировки, в отличие от C++.
Вещественные типы так же отличаются друг от друга числом байтов,
содержащих мантиссу и порядок. Общим и наиболее распространенным для
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
4
всех языков является тип double, занимающий 8 байт памяти и имеющий 15
значащих цифр в мантиссе. Вещественный 4-байтовый тип (7 значащих цифр)
обозначается single в Delphi и float в C-языках. Кроме того, в Delphi и C++
есть 10-байтовый вещественный тип с 18-ью значащими цифрами. В Delphi он
называется extended, а в C++ Borland - long double. В языке C# нет 10байтового вещественного типа, но существует еще один тип числа под
названием decimal. Переменные типа decimal занимают по 16 байт памяти и
имеют 28 значащих цифр, но заметно меньший интервал значений (до ~1028) по
сравнению с другими вещественными типами. Они применяются в финансовых
расчетах. В Delphi соответствующим типом является Currency. Это 8-байтовый
тип чисел с фиксированной запятой и 19-ью значащими цифрами.
К простым типам относятся логические переменные, имеющие два значения true
и false. В Delphi такой тип называется Boolean, и bool в языках C.
Еще одним важным типом, общим для всех языков, является перечислимый тип
(enumerated). Этот тип определяет множество именованных постоянных
величин, хранящих целые числа. В Delphi имеется два перечислимых типа –
обычный и, так называемый, интервальный. В языках C перечислимый тип
обозначается enum.
Массивы, множества, файлы и строки
К сложным, или структурным типам данных относятся в частности массивы
данных одного типа. В Delphi для обозначения массива используется служебное
слово array, к которому через предлог of (так же служебное слово) добавляется
идентификатор типа. Например, описание массива a из 100 целых чисел в
Delphi имеет вид a: array [0..99] of integer. Описание массива в языках C не
содержат служебного слово array. Сами квадратные скобки указывают на то, что
используется массив переменных данного типа. Например, описание того же
массива a из 100 целых чисел в C++ выглядит как int a[100]. В языке C# это же
описание имеет вид int a[] = new int[100].
В Delphi имеется структурный тип set (множество), прямого аналога которому
нет в языках C. Например, описание type Ts set of 1..100 означает в Delphi,
что переменные введенного типа Ts могут содержать в себе любой набор целых
чисел от 1 до 100, или не содержать ни одного целого числа (пустое
множество). К примеру, запись var set1:Ts; set1:= [1, 3, 99] означает, что
переменная set1 имеет тип множества Ts и что ей присвоено значение,
состоящее из трех целых чисел 1, 3 и 99.
Еще один структурный тип file, который есть в Delphi и отсутствует в языках
C. (Не надо думать, что отсутствие специального типа file в языках C
ограничивает эти языки в работе с файловой системой!).
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
5
Тип string в Delphi и C# описывает строку, или последовательность символов.
В языке C++ строка представляется как массив символов. Например, описание
переменной var s:string[100] в Delphi эквивалентно описанию char s[100] в
языке C++. В языке C# строки заданной длины, как в приведенном примере, не
используются, хотя массив символов ввести, конечно, можно, написав char[] s
= new char[100]. Все три языка позволяют использовать строки не
фиксированной длины. В Delphi и C# для этого используется служебное слово
string. Так, описание var s:string в Delphi эквивалентно описанию char s[] в
языке C++ и string s в языке C#.
Структура (запись)
Общим для всех трех языков является структурный тип структура, или запись.
В одной переменной, описанной как тип структура, может храниться
информация разного типа, указанная в конкретном описании этого типа. Для
описания типа структуры в Delphi используется служебный символ record
(запись), а в языках C символ struct. Так описание структуры TRec и
переменной myRec типа TRec в Delphi
type
TRec = record
D:double;
i: integer;
end ;
var myRec:TRec;
эквивалентно описанию
struct TRec
{
double D;
int i;
} myRec;
в C++ и описаниию
struct TRec
{
double D;
int i;
};
TRec myRec;
в языке C#.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
6
Структура в новых версиях Delphi может иметь в качестве полей процедуры или
функции (методы), свойства и ряд других членов, характерных для классов, как
и в языков C.
Класс
Класс или тип класса является структурным типом данных, который состоит из
полей, методов и свойств (property). В языках C# и C++ на платформе .NET к
этому списку добавлен особый тип свойств – события (event). Все вместе –
поля, методы, свойства и события называются членами класса.
 Поля содержат данные, являющиеся составной частью класса. Этим
классы схожи со структурами. Существуют поля экземпляра (объекта)
класса. Эти поля занимают свою одинаковую по объему область памяти у
каждого отдельного экземпляра класса (объекта). Другой тип полей
называется в Delphi полями класса. Поля класса принадлежат именно
типу класса, а не его экземплярам. Одно и то же поле класса будет иметь
одинаковое значение вне зависимости от того, какой экземпляр класса его
использует. Их описание предваряется служебными словами class var.
У всех объектов одного класса есть лишь один экземпляр полей класса. В
языках C аналогом полей класса являются так называемые статические
поля (static). Статические поля в C доступны только через
идентификатор самого класса, но не его экземпляров (объектов). В то же
время, поля класса в Delphi доступны как через идентификатор класса, так
и идентификатор любого его экземпляра (объекта).
 Методы представляют собой подпрограммы (процедуры, или функции),
которые содержат код, управляющий полями объектов (экземпляров)
класса, или полями класса. В Delphi методы могут быть как процедурами,
так и функциями, в языках C – только функциями. Аналогом дельфийских
процедур в C являются функции, возвращающие «пустой» тип
переменных (void). В Delphi имеются методы, являющиеся методами
класса. Их описание предваряется служебным словом class. В языках C
аналогом дельфийских методов класса являются статические методы.
Статические методы вызываются идентификатором класса, но не
экземпляра (объекта). (Это относится к C# и C++ Microsoft). Статические
методы не могут использовать в своем коде нестатические члены класса.
Надо отметить, что в Delphi есть еще статические методы класса. Они
являются практически полным аналогом статических методов в языках C.
 Свойства (properties) представляют собой один или два метода доступа к
полям объекта. Эти методы, именуемые кратко аксессорами (accessor),
обеспечивают интерфейс, связывающий произвол пользователя в задании
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
7
данных с ограничениями на значения данных, хранящихся в полях класса
или объекта. Существуют два типа метода доступа, условно именуемые
set и get. Метод типа set обычно устанавливает значение поля,
соответствующего данному свойству в то значение, которое задано
пользователем. Однако внутри метода set программист может поставить
ограничения, не позволяющие устанавливать в это поле какие-либо
значения, либо меняющие входные данные при передаче их в поле.
Аксессор get возвращает значение соответствующего поля. Сам процесс
возврата может сопровождаться какими-либо дополнительными
вычислениями, предусмотренными программистом внутри метода get.
Любой из аксессоров может присутствовать или отсутствовать в описании
свойства, но хотя бы один должен быть. В Delphi и C++ при описании
свойства используется служебное слово property. В языке C# служебное
слово property не используется, но служебными являются слова set,
value и get (которых нет в Delphi и C++). Слово value несет то значение,
которое передает пользователь методу set в качестве параметра. С точки
зрения пользователя свойства выглядят как обычные данные (поля). Если
свойству Number пользователь присваивает значение 5, то это фактически
означает вызов метода set с параметром value, равным пяти. Если,
наоборот, пользователь хочет получить (вернуть, get) значение поля,
отвечающего свойству Number, передав его, например, переменной n, то
он пишет n <присвоить> Number. Это приводит к вызову метода get
свойства Number. Манипулируя присутствием методов get и set в
описании свойства, его можно сделать свойством «только для чтения»
(есть только метод get), или свойством «только для записи» (есть только
метод set).
 События (event) как выделенный тип методов имеются только в языках
C# и расширении C++ на управляемый код. События - это особые
свойства, с методами доступа add и remove в C# и, дополнительно,
методом raise в C++ CLR. Методы add и remove в качестве параметра
value используют объект, несущий в своем поле ссылку на метод,
выполняемый при наступлении того, или иного события. Этот метод
называют обычно обработчиком события. Обработчик может иметь
лишь специальный тип, совместимый с типом, указанным при описании
события. Аксессор add добавляет value в цепочку методов, вызываемых
при наступлении описываемого события, а remove – удаляет. Скажем,
событие OnClick для класса кнопок предполагает, что кнопка изменит
свой внешний вид. Этим занимается код самого класса кнопок. Но
пользователь может добавить к этому действию свой метод (обработчик
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
8
события Click), который будет выполнен при его наступлении. Метод
raise в C++ CLR вызывает обработчик события и имеет ту же сигнатуру,
что тип события (обработчика).
Обработчиков может быть несколько. Их можно удалять и добавлять к
событию, формируя любую последовательность, т.е. любую реакцию на
событие. Методы add и remove вызываются автоматически оператором
добавления += и, соответственно, оператором вычитания -=. То есть,
запись <событие> += <обработчик> означает, что к цепочке обработчиков
<события> добавился еще один <обработчик>. В отличие от свойств, где
обязательным является описание методов set и/или get, при описании
события можно опустить описание методов add и remove. Они
автоматически будут дописаны транслятором, и сами имена этих методов
служебными словами не являются.
При описании классов во всех трех языках используется служебное слово
class. В Delphi и C# классы являются ссылочными типами. В языке C++
классы являются типами-значениями, если не описаны специальным образом
через указатели или ссылки. Различие между типами-значениями и ссылочными
типами в том, что данные типов-значений хранят сами значения указанного
типа (то есть все его байты), а данные ссылочного типа хранят адреса памяти,
где находятся их значения (то есть только 4 байта с адресом в Win32). При
передаче в качестве параметра в процедуру или функцию переменной типазначения передаются все его поля. Переменная ссылочного типа хранит лишь
адрес области памяти, начиная с которого значение располагается. Для
структурных типов, содержащих большое количество полей, использование
ссылочного типа предпочтительней. Это относится к данным типа class. Данные
типа record и struct в Delphi и C# относятся к типам-значениям. Предполагается,
что объем полей структуры не велик.
Важными особенностями класса являются его наследование и полиморфизм.
Наследование позволяет создавать новые типы классов, используя поля и
методы уже имеющегося класса-предка и не описывая их вновь в классенаследнике. Полиморфизм позволяет использовать так называемые
виртуальные методы, содержание которых различно в классе-наследнике и в
классе-предке.
Использование
виртуальных
методов
упрощает
программирование ситуаций, в которых заранее неизвестно, объект какого из
классов должен выполнить виртуальный метод. Скажем, метод Show
изображает контур эллипса и описан в классе Ellipse. Тот же метод Show
изображает заполненный эллипс и описан в классе FilledEllipse, являющимся
наследником класса Ellipse. Пользователь программы, работая с объектами
класса Ellipse и его наследниками, использует метод Show для разных типов
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
9
эллипса, в зависимости от своих предпочтений. Программист лишь указывает
на то, что вызывается метод Show объектом какого-либо из классов эллипсов, не
уточняя, каким конкретно. Но при этом работать будет метод Show того класса,
объект которого указан пользователем во время работы программы.
Для обеспечения полиморфизма класс формирует так называемую таблицу
виртуальных методов (VMT). Основным назначением VMT является хранение
адресов, по которым хранятся коды виртуальных методов данного класса.
Кроме того, VMT содержит данные об объеме памяти, занимаемым объектом
этого класса. Если, обращаясь к предыдущему примеру, адрес метода Show
занимает третью строчку в VMT класса Ellipse, то он занимает эту третью
строчку в таблицах всех классов-наследников, включая и FilledEllipse. Поэтому
вызов метода Show сводится к обращению объекта к третьей строчке таблицы
виртуальных методов своего класса. Для этого, конечно, объект должен знать
место, где находится VMT класса.
У объекта есть поле, в котором хранится адрес VMT. Заносится адрес VMT в
это поле путем вызова специального метода, который называется
конструктором. В языках C классы имеют конструктор по умолчанию. В языке
C++ вызов конструктора происходит автоматически при описании объекта.
Вызывается так называемый конструктор по умолчанию. Если описывается
ссылка на объект и объект создается динамически, то вызов конструктора
требуется. Однако и в этом случае можно вызывать конструктор по
умолчанию. В языках C# и Delphi классы являются ссылочными типами,
поэтому вызов конструктора требуется всегда. Если конструктор не описан,
то вызывается конструктор по умолчанию. В языке C#, как и в C++,
конструктор по умолчанию не описывается и не имеет параметров. В Delphi
роль конструктора по умолчанию играет конструктор класса-предка.
Вызов конструктора класса, как ссылочного типа, происходит с одновременным
выделением памяти под хранение конструируемого объекта. Для этого в языках
C используется служебное слово new. Создание объекта в Delphi, требует
вызова конструктора как метода класса, т.е. согласно синтаксису <тип
класса>.<имя конструктора>. Объем памяти, необходимый для хранения
объекта, берется конструктором из таблицы VMT класса. Кроме того,
конструктор не только посылает в соответствующее поле объекта адрес VMT
класса, но и инициализирует в ноль все поля объекта.
В языках C конструктором является любой метод, носящий имя класса. В
Delphi конструктором является метод, в описании которого указывается
служебное слово constructor. Обычно используется имя Create.
Освобождение занятой объектом памяти так же требует знание не только
адреса, где объект находится, но и какой объем памяти он занимает. Такую
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
10
информацию сообщает системе управления памятью другой специальный метод
– деструктор. Обычно, одновременно с вызовом деструктора требуется вызов
самой процедуры освобождения. Поэтому в Delphi для освобождения памяти
рекомендуется применять метод Free, унаследованный всеми классами от их
общего предка TObject. Метод Free вызывает деструктор объекта данного
класса и освобождает память. В языках C освобождение памяти обычно
происходит автоматически после выхода кода из той области кода, в которой
объект был создан и действовал. При этом так же вызывается деструктор
класса.
В языках C деструктором является метод класса, носящий имя класса с
префиксом ~ (в языке C знак ~ используется для отрицания). В Delphi при
описании деструктора используется служебное слово destrutor. Обычное имя
для деструктора в Delphi Destroy.
Уровень доступа
Члены класса могут иметь разный уровень доступа. Общими для всех языков
являются три уровня доступа public – полный доступ, protected – доступ
только для членов самого класса и его наследников, private – доступ только
для членов самого класса. Перечисленные служебные слова добавляются к
описанию членов класса и называются модификаторами доступа. Строго
говоря, в Delphi члены класса с уровнями доступа private и protected видны
везде в том модуле, в котором описан класс. В последних версиях Delphi
существуют уровни доступа strict private и strict protected. Члены с
уровнем доступа strict private видны только внутри класса, в котором они
описаны. Члены с уровнем доступа strict protected видны только классамнаследникам, не зависимо от модуля, в котором наследник описан.
В языке C++ существует модификатор friend, который позволяет отдельной
функции или методам целого класса иметь доступ ко всем членам (включая
private и protected) того класса, среди членов которого находится указанная
«дружественная функция» или «дружественный класс». При этом ни
«дружественная функция», ни «дружественный класс» не являются членами
класса, в котором они объявлены.
В дополнение к указанным модификаторам в Delphi существует модификатор
published. Этот уровень доступа эквивалентен public. Дополнительно уровень
published открывает доступ дизайнеру среды, который генерирует
специальную информацию о члене класса, помеченном этим уровнем, сохраняя
ее в специальном файле и воспроизводя на экране в процессе дизайна. Уровень
доступа published имеется и в реализации C++ Builder фирмы Borland. В языке
C# предусмотрен еще один уровень доступа internal, который ограничивает
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
11
доступ лишь классами данной сборки. Сборкой (assembly) называется
совокупность кодовых файлов, которые объединены единой версией и
устанавливаются как единый модуль. Проще говоря, совокупность классов,
которые транслируются в один исполняемый модуль. В свою очередь
исполняемым модулем может быть динамически подключаемая библиотека
(dynamic-link library, или dll) или исполняемый файл приложения (exe-файл).
Отличие между библиотекой и exe-файлом в том, что библиотека не имеет
точки входа и не может выполняться самостоятельно. В то время как
исполняемый модуль имеет точку входа, хотя для своей работы может
нуждаться в присутствии библиотек. В C# каждое приложение или библиотека
может представлять собой набор файлов (сборку, или assembly), которая
транслируется в форме единого модуля, готового к использованию. Возможно
так же сочетание модификаторов доступа protected internal, которое
ограничивает доступ лишь классами самой сборки, или наследниками данного
класса.
Если перед описанием члена класса модификатор доступа не указывается
(доступ по умолчанию), то в Delphi это равносильно модификатору published.
В языках C члены структур struct имеют по умолчанию модификатор доступа
public, а члены классов class модификатор private.
В языке C# модификаторы доступа применимы так же к описанию типов классов. Примеры будут даны ниже. В
языке C++ модификаторы доступа могут указываться в описании класса-наследника в списке базовых классовпредков, тем самым, модифицируя доступ членов классов-предков, видимых из класса-наследника.
Вопросы для самоконтроля
1. Каковы правила написания идентификаторов в Delphi и C?
2. Как записываются 16-ричные постоянные в Delphi и C?
3. Какие целочисленные типы данных существуют в Delphi и языках C?
4. Какие вещественные типы данных есть в Delphi и языках C?
5. Перечислите стандартные структурные типы данных в Delphi и C?
6. Структуры в Delphi и C.
7. Классы и их описание в языках Delphi и C.
8. Поля, свойства, события.
9. Типы-значения vs ссылочные типы.
10.Наследование, полиморфизм, виртуальные методы, таблица VMT.
11.Конструкторы в Delphi и C.
12.Деструкторы и освобождение памяти в Delphi и C.
13.Правила доступа к членам класса в языках Delphi и C.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
12
Операторы
Существует два класса операторов, используемые в языках программирования.
Один класс операторов объединяет собственно операторы (operators),
производящие действия с данными. Каждый оператор этого класса представляет
собой знак (или слитную комбинацию двух знаков) или служебное слово,
обозначающее действие над одним или более операндами. Вместе совокупность
операндов и операторов типа operators формируют выражение (expression).
Примерами таких операторов являются операторы, производящие
арифметические действия - сложение, умножение, деление, вычитание,
обозначаемые одинаково во всех языках знаками +, *, / и – (минус). Другой
класс операторов называется statements. Эти операторы состоят обычно из
нескольких служебных слов и специальных символов. Примером оператора
типа statements является условный оператор if … else, во многом похожий для
всех трех языков.
Operators
Арифметические операторы в Delphi это +, -, *, /, div и mod. Оператор div
определяет деление на цело двух целых операндов. В языках C ему
соответствует обычный знак деления /, примененный к целым операндам.
Оператор mod определяет остаток от деления двух целых операндов. В языках
C ему соответствует знак %. Оператор сложения + во всех языках используется
так же для «сложения» (конкатенации) строк, объединяя две строки-операнда в
одну.
В языках C существуют операторы инкремента ++ и декремента --, которых
нет в Delphi. Оператор инкремента увеличивает значение операнда на единицу,
а декремента – уменьшает на единицу. Аналогом в Delphi являются
стандартные функции Inc и Dec. Если операторы ++ и -- стоят после выражения
(постинкремент, или постдекремент), то выражение в начале вычисляется и
присваивается результату, а затем к нему добавляется (вычитается) единица.
Если же эти операторы расположить перед выражением (преинкремент и
предекремент), то выражение в начале меняется на единицу, а затем уже
присваивается результату.
Логические операторы имеют операндами выражения типа Boolean (bool) или
целого типа. В языке C++ некоторые, указанные ниже, операции могут
осуществляться над выражениями любого скалярного типа.
 Логическое отрицание обозначается not в Delphi. Это оператор ! в языках
C для булевского операнда (в C++ оператор ! применяется и к операндам
любого скалярного типа) и ~ для целого операнда. У оператора отрицания
один операнд.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
13
 Логическое умножение (конъюнкция) обозначается and в Delphi. В языках
C это знак && в применении к булевским операндам (в C++ - к операндам
любого скалярного типа) и & - к операндам целого и булевского типа.
 Логическое сложение (дизъюнкция) обозначается or в Delphi. В языках C
это знак || в применении к булевским операндам (в C++ - к операндам
любого скалярного типа) и | - к операндам целого и булевского типа.
 Симметрическая разность (исключающая дизъюнкция) обозначается xor
в Delphi и ^ в языках C в применении к булевским и целым операндам.
Различие между операторами && и & (так же, как и между операторами || и |) в
применении к булевским операндам (а в C++ и к скалярным операндам) состоит
в том, что в первом случае оценка второго операнда производится только в том
случае, если результат не известен по значению первого операнда. Например,
если в выражении b1 && b2 известно, что b1 равно false, то нет смысла
оценивать b2. Так же, если в выражении b1 || b2 известно, что b1 есть true, то нет
смысла считать b2. Если же требуется в любом случае считать второй операнд
(например, он может быть результатом некоторой функции, которая всегда
должна быть выполнена), то применять следует операторы & и |
соответственно.
При применении логических операторов к операндам целого типа операция
производится над каждым битом операндов.
Применение в языке C++ логических операторов &&, || и ! к произвольному
скалярному типу имеет следующий смысл:
 && возвращает true, если и только если оба операнда не равны нулю.
 || возвращает true, если хотя бы один из операндов не равен нулю.
 ! возвращает true, если операнд равен нулю.
Операторы сдвига в Delphi обозначаются shl (сдвиг влево) и shr (сдвиг
вправо). Их операндами являются целочисленные выражения. К примеру,
операция x shl 2 сдвигает значение целого числа x на два бита влево. В языках
C эти операторы обозначаются << (сдвиг влево) и >> (сдвиг вправо).
Операторы отношения > (больше), < (меньше), >= (больше или равно) и <=
(меньше или равно) обозначаются одинаково в Delphi и языках C. Оператор
равенства обозначается = в Delphi и == в языках C. Оператор неравенства
обозначается <> в Delphi и != в языках C.
В Delphi есть только один оператор присвоения :=. Он присваивает значение
выражения, стоящего в правой части, переменной, находящейся в левой части.
В языках C существует несколько операторов присвоения. Обычный оператор
присвоения обозначается знаком равенства =. В отличие от Delphi оператор
присвоения в C не только передает значение переменной, стоящей в левой
части, но и возвращает это значение. Поэтому в языках C оператор присвоения
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
14
может быть применен многократно a = b = c, в отличие от Delphi, где запись
a:=b:=c не допустима. Кроме обычного оператора присвоения = в языках C есть
операторы вида +=, -=, *=, /=, %=, &=, |=, ^=, >>=, <<=. Эти операторы перед
присвоением производят соответствующую операцию. Например, a += 1 в
начале прибавляет к a единицу, а затем присваивает новое значение a.
Аналогично действуют все остальные операторы присвоения.
В языках C есть оператор условного выражения (не путать с условным
оператором, описанным ниже), которого нет в Delphi. Он выглядит следующим
образом E1?E2:E3. Действие оператора условного выражения сводится к
следующему. В начале вычисляется логическое выражение E1, которое
возвращает true или false. В случае true вычисляется выражение E2, в случае
false – выражение E3. Все выражение в целом возвращает либо E2, либо E3 в
зависимости от выполнения условия E1.
Были перечислены лишь наиболее часто встречающиеся операторы. Есть и
другие, с которыми мы познакомимся в ходе написания конкретного кода.
Statements
Операторы типа statement часто делят на простые и структурированные. К
простым операторам типа statement относятся
 оператор вызова процедуры или функции,
 операторы перехода goto, break, continue, return.
Оператор вызова во всех трех языках состоит из имени вызываемой функции
(или процедуры в Delphi) и круглых скобок с указанием списка фактических
параметров. В Delphi при отсутствии параметров круглые скобки можно
опустить, в языках C круглые скобки при вызове функции пишутся всегда.
Оператор перехода goto обеспечивает переход в указанную область кода,
помеченную меткой. Он одинаков во всех трех языках и в современном
программировании используется исключительно редко. Оператор break во всех
языках прерывает выполнение оператора цикла. В языках C оператор break
используется так же для прерывания выполнения условного оператора типа
switch. Оператор continue во всех языках используется для возврата на
первый оператор следующего шага цикла и оставляет не выполненными
операторы, которые следуют за continue внутри цикла. Оператор return есть
только в языках C. Он используется для выхода из функции.
К структурированным операторам относятся
 составной оператор, ограниченный в Delphi служебными словами begin
и end, а в языках C фигурными скобками {};
 операторы выбора, или условные операторы. Во всех языках существует
два типа условных операторов.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
15
o Оператор типа if … else во всех языках выполняется одинаково и
имеет близкий формат написания. После служебного слова if
должно располагаться логическое выражение. Если значение этого
выражения true, то выполняются операторы, которые в языке Delphi
отделены от логического выражения служебным словом then. В
языках C слова then нет, но логическое выражение должно быть
взято в круглые скобки. В противном случае, либо выполняются
операторы, стоящие за служебным словом else, либо, если нет
служебного слова else, выполнение оператора if прекращается.
o Оператор множественного выбора. В Delphi оператор
множественного выбора имеет формат
case <выражение выбора> of
<постоянная1>: <оператор1>;
…
<постояннаяN>: <операторN>;
else
операторы;
end;
Здесь <выражение выбора> возвращает некоторое значение,
совместимое по типу с постоянными, перечисленными внутри
оператора. Если значение выражения выбора совпадает с
постоянной <постоянная1>, то выполняется <оператор1> и
управление покидает оператор и т.д. Если ни одно из условий не
выполняется, то выполняются операторы, стоящие после else.
Раздел else может отсутствовать. Тогда управление передается
наружу. Тип выражения <выражение выбора> может быть только
целым или символом. Постоянные <постоянная1> и т.д. должны
быть того же типа, или представляеть собой интервал значений
этого же типа, или, наконец, представлять собой список
постоянных, разделенных запятыми.
В языках C тот же оператор множественного выбора имеет
следующий формат.
switch (<выражение выбора>)
{
case <постоянная1>: <оператор1>; break;
…
case <постояннаяN>: <операторN>; break;
default
операторы;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
16
}
Выполняется оператор switch аналогично его дельфийской версии.
Секция default может отсутствовать. В этом случае управление
покидает оператор switch, если ни одно из условий не выполняется.
Строгое описание синтаксиса операторов множественного выбора
можно найти в справочных системах сред.
 Операторы цикла. Существует три типа операторов цикла.
o Оператор цикла с постусловием. В операторе цикла с
постусловием в начале выполняются операторы, стоящие внутри
оператора цикла, а затем проверяется условие окончания цикла. В
Delphi оператор цикла с постусловием имеет формат repeat
оператор1; ...; операторN; until <выражение, возвращающее
условие окончания цикла>. Когда <выражение, возвращающее
условие окончания цикла > возвращает true, цикл прерывается.
Цикл с постусловием в языках C имеет формат do <оператор>
while <выражение, возвращающее условие окончания цикла>.
Здесь цикл выполняется до тех пор пока <выражение,
возвращающее условие окончания цикла> возвращает true.
o Оператор цикла с предусловием. В этом операторе в начале
проверяется условие, а затем выполняются операторы,
находящиеся в теле цикла. В Delphi оператор цикла с предусловием
имеет формат while <выражение, возвращающее условие
окончания цикла> do <оператор> . В языках C этот оператор имеет
аналогичный формат while (<выражение, возвращающее условие
окончания цикла>) <оператор>. Отличие лишь в том, что
отсутствует служебное слово do, но при этом <выражение,
возвращающее условие окончания цикла> заключено в круглые
скобки.
o Оператор цикла с перечислением (цикл типа for) используется при
необходимости явного перечисления шагов цикла с помощью
числового параметра обычно целого типа. В Delphi структура
оператора типа for выглядит следующим образом:
for counter := initialValue to finalValue do statement
или
for counter := initialValue downto finalValue do statement
Здесь counter – «счетчик» цикла, или переменная перечислимого
(чаще всего, целого) типа. Значения счетчика меняются в пределах,
определяемых выражениями initialValue и finalValue. В первом
случае (to) значения счетчика возрастают на 1 за каждый шаг
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
17
цикла, а во втором (downto) – уменьшаются. Оператор statement
выполняется на каждом шаге цикла. Если в первом случае (to)
initialValue > finalValue, или во втором случае (downto) initialValue
< finalValue, то оператор statement не выполняется.
В языках C формат оператора for несколько иной.
for (
<инициализирующее выражение, типа counter = initialValue;
< условие продолжения цикла, типа counter <= finalValue >;
<выражение, меняющее счетчик, типа counter++>)
statement;
В отличие от Delphi в языках C возможны различные формы
инициализирующего выражения, условия продолжения цикла и
различные выражения, меняющие значения счетчика.
В языках Delphi и C существуют и другие типы операторов типа statement. Они
используются реже, чем перечисленные выше. При встрече с ними в
практических приложениях дается необходимый комментарий.
Вопросы для самоконтроля
1. Арифметические операторы в Delphi и C.
2. Операторы инкремента и декремента в C.
3. Логические операторы в Delphi и C.
4. Операторы сдвига в Delphi и C.
5. Операторы отношения.
6. Операторы присвоения в Delphi и C.
7. Оператор условного выражения в C.
8. Операторы вызова подпрограммы в Delphi и C.
9. Операторы перехода в Delphi и C.
10.Составной оператор в Delphi и C.
11.Операторы выбора типа if в Delphi и C.
12.Операторы множественного выбора в Delphi и C.
13.Операторы цикла с предусловием в Delphi и C.
14.Операторы цикла с постусловием в Delphi и C.
15.Операторы цикла с перечислением в Delphi и C.
Среды, классы, приложения
В этой части курса рассмотрены примеры конкретных классов и приложений
разного типа, реализованных на языках Delphi, C++ и C# в средах CodeGear
Borland и MS Visual Studio.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
18
Введение
В дальнейшем рассматриваются классы, реализующие численные алгоритмы
решения некоторых стандартных математических задач. Эти классы
отсутствуют в стандартных библиотеках перечисленных сред, но имеют важное
прикладное значение для составления приложений, моделирующих физические
процессы. В тексте нет математических формул, которые приводятся во всех
стандартных курсах по численным методам. Здесь приводятся лишь алгоритмы,
реализованные в конкретных языках. В кодах есть комментарии, которые
поясняют особенности языков, и кратко - содержание алгоритмов.
Кроме кодов самих классов и комментариев к ним, приводятся примеры
приложений, тестирующих эти классы. Приложения эти двух типов.
Консольные, или приложения «в черном окне», и локальные оконные. Оконные
приложения используют возможности сред в построении дизайна интерфейса и
стандартные классы библиотек. Это помогает получить опыт в создании
оконных приложений в современных средах.
В качестве примера в начале рассматриваются классы, реализующие численное
решение нелинейного алгебраического уравнения f(x) = 0 различными
методами.
Существует множество алгоритмов, сужающих интервал изоляции корня
трансцендентного уравнения. Основу нашей иерархии классов, реализующих
некоторые из этих алгоритмов, составляет абстрактный класс, поставляющий
общий для всех его наследников набор методов и свойств. Шесть классовнаследников реализуют методы деления пополам, секущей, ложного
положения, Ридерса, Брента и Ньютона-Рафсона. Описание алгоритмов этих
методов можно найти в книгах, посвященных численным методам. Посмотрите,
например, ссылку.
Среда CodeGear Borland. Delphi
Delphi
В языке Delphi код класса (или нескольких классов, близких по смыслу)
принято писать в отдельном физическом файле – модуле, начинающимся
служебным словом unit, с говорящим именем. Модуль Delphi имеет
стандартную для этого языка структуру – интерфейс, видимый всем другим
модулям, использующим данный, раздел реализации (implementation),
доступный лишь внутри данного модуля, и, возможно, раздел инициализации.
В интерфейсе код не пишется. Там перечисляются лишь те типы, переменные и
заголовки процедур и функций, которые доступны внешним модулям. В разделе
реализации дается реализация, указанных в интерфейсе процедур, функций или
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
19
методов классов. В разделе реализации описание так же допустимо, и оно не
видимо для внешнего кода. Раздел инициализации, если он существует, следует
за разделом реализации, и начинается словом begin. В нем дается код, который
должен быть выполнен в самом начале приложения, ссылающегося на данный
модуль, и сразу после его окончания. Если есть код, который должен быть
выполнен после завершения приложения, то в разделе инициализации следует
выделить две секции. Первая секция собственно инициализации выделяется
служебным словом initialization. Секция завершения существет лишь в том
случае, если есть секция инициализации. Секция завершения отделяется от
инициализирующей секции служебным словом finalization и продолжается
до конца модуля.
В описанном ниже модуле раздел инициализации отсутствует.
Модуль классов. Интерфейс
{
В описании модуля за словом unit должно следовать имя модуля (в данном случае,
uNonLinearEquation), совпадающее с именем файла.
Комментарий в Delphi можно размещать в конце строки, ограничив его слева двойным
слэшем //, либо в нескольких строках, ограничив его фигурными скобками, как здесь, либо
скобками вида, изображенного ниже.
}
/* это тоже комментарий */
unit uNonLinearEquation;
// В модуле реализованы алгоритмы 7 классов, решающих нелинейное уравнение f(x)=0
// на заданном интервале изоляции шестью различными методами.
// Исходным классом является абстрактный класс TNonLinearEquation.
// Его 6 наследников реализуют конкретные алгоритмы решения уравнения:
// методом деления пополам (TNonLinearEqBisection);
// методом секущей (TNonLinearEqSecant);
// методом ложного положения (TNonLinearEqFalsePos);
// методом Ридерса (TNonLinearEqRidders);
// методом Брента (TNonLinearEqBrent);
// и методом Ньютона-Рафсона (TNonLinearEqNewton));
// интерфейсная секция начинается служебным словом interface, за которым, возможно,
// следует служебное слово uses и список модулей, используемых в интерфейсной секции
// или во всех секциях модуля.
interface uses SysUtils,Classes;
// Условием завершения решения является приближение к корню до некоторого
// минимального по абсолютной величине расстояния (абсолютная погрешность),
// либо относительно малого расстояния по отношению к значению самого корня
// (относительная погрешность)
// служебное слово const предваряет описание данных, остающихся неизменными
// в процессе вычислений
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
20
const
MinRelAcc=1e-16;
//минимальная допустимая относительная погрешность
MaxRelAcc=0.01;
//максимальная допустимая относительная погрешность
DefRelAcc=MinRelAcc; //относительная погрешность по умолчанию
MinAbsAcc=1e-15;
//минимальная допустимая абсолютная погрешность
DefAbsAcc=MinAbsAcc; //абсолютная погрешность по умолчанию
// служебное слово type предваряет описание типов данных, используемых в модуле
// или (в случае интерфейсной секции) вне этого модуля
type
TNonLinearEqClass=class of TNonLinearEquation; //Тип класса
//Тип метода левой части уравнения f(x)=0
TLeftHandSide=function(x: double): double of object;
//Тип метода - обработчика события, наступающего после каждого шага итерации
// Параметр sender определяет ссылку на объект, который использует обработчик
TOnIteration=function (sender: TObject): Boolean of object;
//Абстрактный класс решения уравнения f(x)=0. В нем есть абстрактный метод DoIteration
TNonLinearEquation=class //класс наследует от TObject библиотеки VCL
// Поля и методы класса, доступные только внутри класса или модуля
private
// Поля
FRelAcc,FAbsAcc: double;
//хранят текущие погрешности
FLeftHandSide: TLeftHandSide;//хранит указатель на левую часть уравнения f(x)=0
FRoot: double;
//хранит текущее значение корня
// Хранят значения аргумента и функции на левой и правой границах интервала изоляции
FLeftSideArg, FRightSideArg: double;
FLeftSideFunc, FRightSideFunc: double;
FDelta:double;
//хранит текущую неопределенность в значении корня (>0)
// Поля FOnBeforeIterations, FOnAfterIterations хранят указатели на обработчики событий,
// Событие Before Iteration наступает перед началом итерационного цикла.
// Событие After Iteration наступает сразу после завершения итерационного цикла.
// Тип обработчика TNotifyEvent является стандартным и описан в модуле Classes
// библиотеки VCL в формате procedure (sender: TObject) of object.
FOnBeforeIterations, FOnAfterIterations: Classes.TNotifyEvent;
// Событие On Iteration наступает в конце каждой итерации,
// если метод DoIteration не прерывает итерационный цикл, возвратив true.
// Поле FOnIteration хранит указатель обработчика события On Iteration.
FOnIteration: TOnIteration;
// Методы, не доступные вне классов, описанных в данном модуле
// Реализует итерационный цикл
function BasicLoop: double;
// Устанавливает текущее значение абсолютной погрешности
// Модификатор const указывает,
// что параметр метода не должен изменяться внутри метода
procedure SetAbsAcc (const value: double);
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
21
// Устанавливает текущее значение относительной погрешности
procedure SetRelAcc (const value: double);
// Методы, доступные наследникам.
protected
// Метод Initialize инициализирует поля, используемые наследниками.
// Хотя он не абстрактный, но пустой в этом классе.
// Вызывается внутри BasicLoop до входа в основной цикл
// и непосредственно перед наступлением события Before Iterations.
procedure Initialize;virtual;
// Абстрактный метод выполнения одной итерации приближения к корню.
// Метод DoIteration реализует алгоритм поиска корня на одном шаге итерации.
// В настоящем классе является абстрактным и должен быть перекрыт наследником.
// Своими двумя параметрами по ссылке метод DoIteration должен вернуть
// текущие значения корня CurRoot и его неопределенность CurDelta (>0).
// Параметры по ссылке требуют модификатора var в своем описании.
// Параметры по ссылке передают в метод только свои адреса, но не значения.
// Поэтому, если значение параметра по ссылке меняется внутри метода, то
// изменяется значение фактического параметра, переданного методу
// Параметры по значению передают свои значения.
// Эти значения копируются при вызове метода.
// Поэтому фактические параметры, переданные по значению, остаются неизменными
// даже если их копии меняются при выполнении метода.
// Эти параметры используются в условии завершения итерационного цикла.
// Кроме того, метод DoIteration возвращает собственное условие завершения
// итерационного цикла логическими значениями true или false.
function DoIteration (var CurRoot,CurDelta: double): Boolean;virtual;abstract;
// Свойства и методы, доступные любому приложению
public
// Свойства
// возвращает и устанавливает текущее значение абсолютной погрешности
// Для указания на методы или поля, возвращающие и устанавливающие свойство
// используются служебные слова read и write соответственно.
// Метод, устанавливающий свойство, должен иметь один параметр по значению
// того же типа, что и тип свойства, и должен быть процедурой .
// Метод, возвращающий значение свойства, должен быть функцией без параметров,
// возвращающий переменную того же типа, что и свойство.
// Для описания свойства используется служебное слово property
property AbsAcc: double read FAbsAcc write SetAbsAcc;
// возвращает текущую неопределенность в значении корня (>0)
property Delta: double read FDelta;
// возвращает и устанавливает ссылку на левую часть уравнения f(x) = 0
property LeftHandSide: TLeftHandSide read FLeftHandSide write FLeftHandSide;
// возвращает значение аргумента на левой границе начального интервала изоляции
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
22
property LeftSideArg: double read FLeftSideArg;
// возвращает значение функции на левой границе начального интервала изоляции
property LeftSideFunc: double read FLeftSideFunc;
// Свойства, устанавливающие указатели на обработчики событий класса
property OnAfterIterations: Classes.TNotifyEvent write FOnAfterIterations;
property OnBeforeIterations: TNotifyEvent write FOnBeforeIterations;
property OnIteration: TOnIteration write FOnIteration;
// возвращает и устанавливает текущее значение относительной погрешности
property RelAcc: double read FRelAcc write SetRelAcc;
// возвращает значение аргумента на правой границе начального интервала изоляции
property RightSideArg: double read FRightSideArg;
// возвращает значение функции на правой границе начального интервала изоляции
property RightSideFunc: double read FRightSideFunc;
// возвращает текущее значение корня
property Root: double read FRoot;
// Конструктор по умолчанию присваивает полям класса нулевые значения
// В данном случае конструктор устанавливает значения
// абсолютной и относительной погрешностей равными постоянным,
// описаным в начале интерфейсной секции.
constructor Create;
// Метод GetRoot определяет и возвращает корень уравнения
// на интервале изоляции [aLeftSideArg; aRightSideArg]
function GetRoot (const aLeftSideArg, aRightSideArg: double): double;virtual;
end;
//Типы классов исключительных ситуаций,
// используемых в методах класса TNonLinearEquation и его наследников.
// Тип класса исключительной ситуации, порождаемой
// в том случае, когда не задан аргумент метода или какое-либо поле
EArgumentNullException = class (SysUtils.Exception);
// Тип класса исключительной ситуации, порождаемой
// в том случае, когда аргумент или параметр выходит
// за границы требуемого интервала
EArgumentOutOfRangeException = class (SysUtils.Exception);
// Тип класса исключительной ситуации, порождаемой
// в том случае, когда аргумент не удовлетворяет требуемым условиям
EArgumentException = class (SysUtils.Exception);
// Класс решает уравнение f(x) = 0 методом деления отрезка пополам (Bisection)
TNonLinearEqBisection=class (TNonLinearEquation)
private
// Поля, хранящие текущие значения аргумента, функции и неопределенности корня
FCurArg, FCurFunc, FCurDelta: double;
protected
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
23
// Метод, реализующий алгоритм поиска корня методом деления пополам
// Модификатор override означает, что метод виртуальный и перекрывает
// тот же метод у предка.
// Все виртуальные методы у наследников должны иметь модификатор override
function DoIteration(var CurRoot,CurDelta: double): Boolean;override;
// Метод, инициализирующий поля класса перед входом в итерационный цикл
procedure Initialize;override;
end;
// Класс решает уравнение f(x) = 0 методом секущих.
TNonLinearEqSecant=class (TNonLinearEquation)
private
// Поля текущих значений аргумента и функции последнего приближения
FLastArg, FLastFunc,
// Поля текущих значений аргумента, функции и неопределенности корня
FCurArg, FCurFunc, FCurDelta: double;
protected
// Метод, реализующий алгоритм поиска корня методом секущих
function DoIteration (var CurRoot,CurDelta: double): Boolean;override;
// Метод, инициализирующий поля класса перед входом в итерационный цикл
procedure Initialize;override;
end;
// Класс решает уравнение f(x) = 0 методом ложного положения
TNonLinearEqFalsePos=class (TNonLinearEquation)
private
// Поля, хранящие текущие значения аргумента и функции на границах
// с отрицательным и положительным значениями функции
FNegSideArg, FPosSideArg,
FNegSideFunc, FPosSideFunc,
//Поле, хранящее текущую разность значений аргумента на краях интервала
FDeltaArg,
// Поля, хранящие текущую неопределенность корня
// и текущие значения аргумента и функции.
FCurDelta, FCurArg, FCurFunc: double;
protected
// Метод, реализующий алгоритм поиска корня методом ложного положения
function DoIteration (var CurRoot,CurDelta: double): Boolean;override;
// Метод, инициализирующий поля класса перед входом в итерационный цикл
procedure Initialize;override;
end;
//Класс решает уравнение f(x) = 0 методом Ридерса
TNonLinearEqRidders=class (TNonLinearEquation)
private
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
24
// Поля, хранящие текущие значения аргумента и функции
// на краях интервала изоляции корня.
FLeftArg, FRightArg, FLeftFunc, FRightFunc,
// Поля, хранящие текущие значения аргумента и функции
FCurArg, FCurFunc: double;
protected
// Метод, реализующий алгоритм поиска корня методом Ридерса
function DoIteration (var CurRoot,CurDelta: double): Boolean;override;
// Метод, инициализирующий поля класса перед входом в итерационный цикл
procedure Initialize;override;
end;
// Класс решает уравнение f(x) = 0 методом Брента
TNonLinearEqBrent=class (TNonLinearEquation)
private
// Поля, хранящие текущие значения аргумента и функции,
// в трех точках интервала изоляции корня
FFirstPointArg, FFirstPointFunc,
FSecondPointArg, FSecondPointFunc,
FThirdPointArg, FThirdPointFunc,
// и текущие значения разделяющих эти точки интервалов
FFirstDx, FSecondDx: double;
protected
// Метод, реализующий алгоритм поиска корня методом Брента
function DoIteration (var CurRoot,CurDelta: double): Boolean;override;
// Метод, инициализирующий поля класса перед входом в итерационный цикл
procedure Initialize;override;
end;
// Класс решает уравнение f(x) = 0 методом Ньютона-Рафсона.
TNonLinearEqNewton=class (TNonLinearEquation)
private
// Поля, хранящие текущие значения аргумента на краях интервала изоляции корня.
FLowArg, FHighArg,
// Поля, хранящие текущую и прешествующую неопределенность корня.
FDeltaArg, FPrevDeltaArg,
// Поля, хранящие текущие значения аргумента, функции и ее производной.
FCurArg, FCurFunc, FCurDerivative: double;
// Поле, хранящее ссылку на функцию, определяющую производную функции f(x)
FDerivative: TLeftHandSide;
protected
// Метод, реализующий алгоритм поиска корня методом Ньтона-Рафсона.
function DoIteration (var CurRoot,CurDelta: double): Boolean;override;
// Метод, инициализирующий поля класса перед входом в итерационный цикл.
procedure Initialize;override;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
25
public
// Свойство, устанавливающее и возвращающее указатель
// на производную функции f(x).
property Derivative: TLeftHandSide read FDerivative write FDerivative;
// Метод GetRoot определяет и возвращает корень уравнения
// на интервале изоляции [aLeftSideArg;aRightSideArg] для метода Ньютона.
function GetRoot (const aLeftSideArg,aRightSideArg: double): double;override;
end;
На этом заканчивается раздел интерфейса модуля uNonLinearEquation.Ссылка на
текст интерфейса модуля.
Комментарии к интерфейсу
В общем случае интерфейс модуля в Delphi содержит описания типов
(служебное слово type), переменных (var), постоянных (const), процедур
(procedure) и функций (function). Описание дается в принципе в
произвольном порядке. Но, если, например, описание переменной или
постоянной включает в себя ссылку на некоторый тип, то этот тип должен быть
описан выше. Например, в нашем модуле есть описание поля
FLeftHandSide: TLeftHandSide;
Оно включает в себя ссылку на тип TLeftHandSide. Поэтому описание типа
TLeftHandSide располагается выше.
В этом правиле есть исключения. Так, в нашем модуле описан тип
TNonLinearEqClass=class of TNonLinearEquation;
И это описание включает в себя тип TNonLinearEquation, описанный ниже. Такое
допустимо.
Напомню, что типы – это указания на то, что представляют собой объекты
(переменные), в дальнейшем описанные этими типами. Существуют простые
типы, такие как double, Boolean и т.д. Они известны транслятору, и описывать их
как типы не следует. Известна и структура объектов, относящихся к этим
стандартным
типам.
Но
транслятор
позволяет
описывать
свои,
пользовательские типы. Имена пользовательских типов в Delphi принято
начинать с буквы T.
Переменные типа класса TNonLinearEqClass=class of TNonLinearEquation имеют
значения ссылок (адресов) на таблицы виртуальных методов классов типа
TNonLinearEquation и его наследников. Такие типы описываются в Delphi с
помощью служебного словосочетания class of.
Смысл использования переменных этого типа состоит в следующем.
Предположим, что мы намерены написать код либо приложения, либо просто
нового класса, в котором будут использоваться классы-наследники
TNonLinearEquation. Но при этом мы хотим, чтобы конкретный тип используемого
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
26
класса не был фиксирован заранее, а являлся бы переменной величиной. В этом
случае переменной становится сам тип класса (его еще называют мета
классом). Именно тип такой переменной и описан под именем TNonLinearEqClass.
Два типа
//Тип метода левой части уравнения f(x)=0
TLeftHandSide=function (x: double): double of object;
//Тип метода - обработчика события, наступающего после каждого шага итерации
TOnIteration=function (sender: TObject): Boolean of object;
имеют окончанием служебное словосочетание of object. Оно означает, что
описаны типы переменных, являющихся ссылками на методы класса. Эти
методы должны иметь определенную сигнатуру. А именно, методы типа
TLeftHandSide должны быть функциями с одним параметром типа double и
должны возвращать значения типа double. Методы типа TOnIteration также
должны быть функциями, но с параметром типа TObject и возвращать должны
значения типа Boolean.
Методы класса отличаются от обычных процедур или функций наличием в них
скрытого параметра self. Параметр self содержит ссылку на объект класса,
вызывающего данный метод.
Типы TLeftHandSide и TOnIteration нужны для того, чтобы классы приложений,
ссылающиеся на наш модуль и использующие класс TNonLinearEquation и его
наследников, могли описать свои функции – левые части трансцендентных
уравнений типа TLeftHandSide и свой метод - обработчик события типа
TOnIteration. Обработчик события - это метод, который пользователь может
написать в своем классе с тем, чтобы производить определенные действия
(например, перерисовывать график интерфейса, или проверять наличие
вызовов, поступающих в приложение от системных устройств – таймера,
мышки, клавиатуры и т.п.) в определенной точке кода, где обработчик
вызывается. В данном случае событие OnIteration наступает на каждом шаге
цикла при сужении интервала изоляции корня трансцендентного уравнения. Это
будет видно из реализации методов класса TNonLinearEquation.
Далее в интерфейсе модуля дается описание семи классов – класса
TNonLinearEquation и шести его наследников.
Описание класса обязательно должно располагаться в разделе описания типов
(после слова type). При этом, в начале указвается имя класса, затем знак
равенства, служебное слово class и после него в круглых скобках имя классапредка). Если предком является класс TObject, то его можно не указывать.
Далее описываются члены класса. Завершается описание класса служебным
словом end.
Любой тип класса является структурным типом, состоящим из членов класса.
Члены класса в Delphi подразделяются на поля, свойства и методы.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
27
Поля – это переменные заданного типа, в которых собственно и хранится вся
статическая информация о конкретном объекте данного класса.
Идентификаторы полей в Delphi принято начинать буквой F от field – поле.
Обычно поля описываются в секции private класса. Это делает их доступными
только методам самого класса. Смысл величин, хранящихся в полях классов,
указан в комментариях.
Свойства регулируют доступ к полям класса и обычно описываются в секции
public. Часть свойств класса TNonLinearEquation, такие как Delta, Root и ряд
других, указывают своим описанием на то, что соответствующие им поля
доступны только для чтения. Другие свойства, такие как OnAfterIterations,
доступны только для записи. Наконец, ряд свойств, такие как AbsAcc и RelAcc
доступны как для записи, так и для чтения, но при этом у этих свойств запись
соответствующего поля проводится методом типа set. А вот свойство
LeftHandSide позволяет и читать и писать напрямую поле FLeftHandSide, как если
бы это поле было описано в секции public.
Методы меняют значения полей класса в соответствие с логикой, для
реализации которой класс создается. Методы являются носителями
программного кода.
Поле всегда что-то хранит, а свойства и методы что-то делают.
В Delphi поля класса описываются перед методами в списке с одним и тем же
правом доступа (private или protected или public). При описании полей в
начале пишется идентификатор поля, выбираемый программистом, а затем,
через двоеточие указывается тип поля. Если несколько полей имеют
одинаковый тип, то их идентификаторы можно перечислять через запятую.
Среди скрытых модификатором доступа private методов мы видим методы
SetAbsAcc и SetRelAcc, устанавливающие поля FAbsAcc и FRelAcc. В той же секции
описан метод BasicLoop, реализующий основной цикл сужения интервала
изоляции.
В зависимости от своего назначения методы класса могут быть процедурами
или функциями, иметь параметры, или не иметь их вовсе. Как методы класса
они всегда имеют один неявный параметр, являющийся текущим объектом
класса и обозначаемый в Delphi словом Self. Другими словами, переменная Self
всегда присутствует внутри любого метода и хранит ссылку на текущий
объект (экземпляр) этого класса.
В секции protected класса TNonLinearEquation описаны методы Initialize и
DoIteration. Оба они относятся к так называемым виртуальным методам, о чем
говорит сопровождающее их заголовок служебное слово virtual. Виртуальные
методы класса могут иметь разное содержание у потомков. Виртуальный метод
DoIteration является абстрактным. Он не содержит никакого кода. Об этом
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
28
говорит служебное слово abstract в его описании. Наличие абстрактного
метода делает весь класс TNonLinearEquation абстрактным, не позволяя создавать
объекты (или, экземпляры) этого класса. Абстрактный класс объединяет код,
общий для всех потомков и существует только ради своих наследников.
Любой наследник абстрактного класса, способный создавать объекты должен
реализовать абстрактный метод DoIteration.
Секция описания класса TNonLinearEquation с модификатором доступа public
содержит описание метода GetRoot. Этот основной метод, который должен
возвращать решение уравнения на интервале изоляции [aLeftSideArg;
aRightSideArg].
У любого класса есть конструктор и деструктор. Конструктор (принятое в
Delphi обозначение конструктора Create) вызывается при создании объекта
класса для инициализации в объекте поля адреса таблицы виртуальных методов.
Деструктор (принятое в Delphi обозначение деструктора Destroy) вызывается
при освобождении памяти от объекта для определения объема памяти,
занимаемого объектом, по таблице виртуальных методов его класса. В языке
Delphi класс может не содержать описание конструктора и/или деструктора
явно. В этом случае вызывается конструктор и/или деструктор класса-предка. В
классе TNonLinearEquation есть описание конструктора. Оно расположено в
секции public. Деструктором класса TNonLinearEquation служит деструктор его
предка – класса TObject.
Класс TObject является предком всех классов библиотеки VCL (Visual Component
Library) в Delphi. В том числе и предком класса TNonLinearEquation. Это означает,
что класс TNonLinearEquation наследует все функциональные способности класса
TObject. Например, в классе TNonLinearEquation не описан явно метод Free,
вызывающий деструктор и реально освобождающий память от объекта. Но
метод Free является одним из членов класса TObject и доступен всем его
потомкам. Поэтому объекты класса TNonLinearEquation могут вызывать метод Free.
В интерфейсной секции модуля uNonLinearEquation описаны три класса
наследника стандартного класса Exception из библиотечного модуля SysUtils. По
правилам синтаксиса языка Delphi предок класса указывается в его заголовке в
круглых скобках после служебного слова class.
// Тип класса исключительной ситуации, порождаемой
// в том случае, когда не задан аргумент метода или какое-либо поле
EArgumentNullException = class (SysUtils.Exception);
// Тип класса исключительной ситуации, порождаемой
// в том случае, когда аргумент или параметр выходит
// за границы требуемого интервала
EArgumentOutOfRangeException = class (SysUtils.Exception);
// Тип класса исключительной ситуации, порождаемой
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
29
// в том случае, когда аргумент не удовлетворяет требуемым условиям
EArgumentException = class (SysUtils.Exception);
Экземпляры классов исключительной ситуации создаются в случае, если при
выполнении кода возникает эта самая исключительная ситуация (ИС), не
позволяющая разумно выполнять дальнейшую часть кода. Например,
знаменатель какой-то дроби обращается в ноль и т.п. Получив объект ИС,
программист может запрограммировать реакцию на исключительную ситуацию.
Об этом ниже.
В интерфейсе модуля uNonLinearEquation описаны также шесть классов наследников класса TNonLinearEquation. Все они реализуют метод DoIteration и
дают свою версию виртуального метода Initialize. Все наследники добавляют
свои поля, а класс TNonLinearEqNewton добавляет свойство Derivative и дает свою
версию виртуального метода GetRoot.
Раздел реализации. Абстрактный класс
implementation uses Math;
// Реализация конструктора
constructor TNonLinearEquation.Create;
begin
// Вызов конструктора предка, как и любого метода предка
// обозначается в Delphi служебным словом
inherited;
AbsAcc:=DefAbsAcc; RelAcc:=DefRelAcc;
end {Create};
// Реализация методов с доступом private
// Итерационный цикл
function TNonLinearEquation.BasicLoop:double;
begin
Initialize; // Инициализация полей в наследниках
// Вызов обработчика перед началом цикла итераций
// Стандартная функция Assigned в Delphi возвращает true,
// если ее аргумент имеет ссылку, отличную от nil (nil - ссылка «в никуда»),
// и false в противном случае.
if Assigned(FOnBeforeIterations) then FOnBeforeIterations(Self);
// Основной цикл
repeat
// выражение, стоящее после until определяет условие прекращения цикла
// Это условие требует, чтобы
// либо метод, реализующий алгоритм итерации, возвращал true,
// либо текущее значение корня покинуло интервал изоляции,
// либо обработчик FOnIteration (если он задан, что проверяется функцией Assigned),
// возвратил true,
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
30
// либо, наконец, допустимая погрешность достигнута.
until DoIteration(FRoot,FDelta) // Вызов алгоритма итерации
or
// Текущий корень покинул интервал изоляции
(FRoot>FRightSideArg) or (FRoot<FLeftSideArg)
or
// Вызов обработчика FOnIteration, если он задан
Assigned(FOnIteration) and FOnIteration(Self)
or
// Условие прекращения цикла по допустимой погрешности
(FDelta<2.0*FRelAcc*abs(FRoot)+0.5*FAbsAcc);
// Вызов обработчика после конца цикла итераций, если он задан
if Assigned(FOnAfterIterations) then FOnAfterIterations(Self);
// При выходе корня за пределы интервала изоляции создается объект ИС
if (FRoot>FRightSideArg) or (FRoot<FLeftSideArg) then
// Создается объект исключительной ситуации типа EArgumentOutOfRangeException.
// Объект несет в себе специальное сообщение об этой ситуации.
// Это сообщение передается конструктору объекта в качестве параметра-строки
raise EArgumentOutOfRangeException.Create
('Алгоритм поиска покинул интервал изоляции!');
// Результатом функции является значение корня.
// Идентификатор Result зарезервирован Delphi для хранения результата функции
Result:=FRoot
end {BasicLoop};
// Устанавливает абсолютную погрешность
procedure TNonLinearEquation.SetAbsAcc(const value:double);
begin
// Если задаваемая погрешность меньше минимально допустимой,
// то устанавливается минимально допустимое значение,
// хранящееся в постоянной MinAbsAcc, описанной в начале модуля
if value<MinAbsAcc then FAbsAcc:=MinAbsAcc else FAbsAcc:=value;
end {SetAbsAcc};
// Устанавливает относительную погрешность
procedure TNonLinearEquation.SetRelAcc(const value:double);
begin
// Если задаваемая погрешность меньше минимально допустимой,
// то устанавливается минимально допустимое значение.
// Если задаваемая погрешность больше максимально допустимой,
// то устанавливается максимально допустимое значение
if value<MinRelAcc then FRelAcc:=MinRelAcc else
if value>MaxRelAcc then FRelAcc:=MaxRelAcc else FRelAcc:=value;
end {SetRelAcc};
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
31
// Реализация методов с доступом protected
// Метод инициализации в этом классе пуст
procedure TNonLinearEquation.Initialize;
begin
end {Initialize};
// Метод поиска решения уравнения на интервале изоляции [aLeftSideArg;aRightSideArg]
function TNonLinearEquation.GetRoot(const aLeftSideArg,aRightSideArg:double):double;
begin
if not Assigned(FLeftHandSide) then
// Создается объект исключительной ситуации типа EArgumentNullException
raise EArgumentNullException.Create('Уравнение не задано!');
if aLeftSideArg>=aRightSideArg then
// Создается объект исключительной ситуации типа EArgumentOutOfRangeException
raise EArgumentOutOfRangeException.Create('Левая граница больше правой!');
// Инициализируются некоторые поля
FDelta:=0.0;
FLeftSideArg:=aLeftSideArg; FRightSideArg:=aRightSideArg;
// Проверяются значения на границах
FLeftSideFunc:=FLeftHandSide(FLeftSideArg);
if FLeftSideFunc=0.0 then
begin
// Корень находится на левом краю интервала изоляции
FRoot:=FLeftSideArg;
// Результат оказывается в переменной Result.
// Стандартная процедура Exit прерывает выполнение метода
Result:=FRoot; Exit
end;
FRightSideFunc:=FLeftHandSide(FRightSideArg);
if FRightSideFunc=0.0 then
begin
// Корень находится на правом краю интервала изоляции
FRoot:=FRightSideArg; Result:=FRoot;Exit
end;
// Если знаки функции на краях интервала изоляции одинаковы, то создается объект ИС
if FLeftSideFunc*FRightSideFunc>0.0 then
// Создается объект исключительной ситуации типа EArgumentException
raise EArgumentException.Create('Это не интервал изоляции!');
// Задается исходное, приближенное значение корня и неопределенности
FRoot:=0.5*(FRightSideArg+FLeftSideArg);
FDelta:=FRightSideArg-FLeftSideArg;
// Вызывается метод итерационного цикла
FRoot:=BasicLoop;
// Функция возвращает значение, помещенное в
// зарезервированную Delphi переменную Result
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
32
Result:=FRoot;
end {GetRoot};
Реализация методов классов Bisection и Secant
// Методы класса TNonLinearEqBisection (метод деления пополам)
// Реализация алгоритма деления интервала пополам
function TNonLinearEqBisection.DoIteration(var CurRoot,CurDelta:double):Boolean;
var tempArg:double;
begin
FCurDelta:=0.5*FCurDelta;
tempArg:=FCurArg+FCurDelta;
CurDelta:=abs(FCurDelta); CurRoot:=tempArg;
FCurFunc:=LeftHandSide(tempArg);
if FCurFunc<=0.0 then FCurArg:=tempArg;
Result:=FCurFunc=0.0
end {Bisection.DoIteration};
// Инициализация полей класса
procedure TNonLinearEqBisection.Initialize;
begin
if LeftSideFunc>0.0 then
begin
FCurDelta:=LeftSideArg-RightSideArg; FCurArg:=RightSideArg; FCurFunc:=RightSideFunc
end else
begin
FCurDelta:=RightSideArg-LeftSideArg; FCurArg:=LeftSideArg; FCurFunc:=LeftSideFunc
end;
end {Bisection.Initialize};
// Реализация методов класса TnonLinearEqSecant (метод секущей)
// Реализация алгоритма секущей
function TNonLinearEqSecant.DoIteration(var CurRoot,CurDelta:double):Boolean;
begin
FCurDelta:=(FLastArg-FCurArg)*FCurFunc/(FCurFunc-FLastFunc);
FLastArg:=FCurArg; FLastFunc:=FCurFunc;
FCurArg:=FCurArg+FCurDelta; FCurFunc:=LeftHandSide(FCurArg);
CurRoot:=FCurArg; CurDelta:=abs(FCurDelta); Result:=FCurFunc=0.0
end {Secant.DoIteration};
// Инициализация полей класса
procedure TNonLinearEqSecant.Initialize;
begin
if abs(LeftSideFunc)<abs(RightSideFunc) then
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
33
begin
FCurArg:=LeftSideArg; FCurFunc:=LeftSideFunc;
FLastArg:=RightSideArg; FLastFunc:=RightSideFunc;
end else
begin
FCurArg:=RightSideArg; FCurFunc:=RightSideFunc;
FLastArg:=LeftSideArg; FLastFunc:=LeftSideFunc;
end;
end {Secant.Initialize};
Реализация методов классов FalsePos и Риддерса
// Реализация методов класса TNonLinearEqFalsePos (метод ложного положения)
// Реализация алгоритма ложного положения
function TNonLinearEqFalsePos.DoIteration(var CurRoot,CurDelta:double):Boolean;
begin
FCurArg:=FNegSideArg+FDeltaArg*FNegSideFunc/(FNegSideFunc-FPosSideFunc);
FCurFunc:=LeftHandSide(FCurArg);
if FCurFunc<0.0 then
begin
FCurDelta:=FNegSideArg-FCurArg;
FNegSideArg:=FCurArg; FNegSideFunc:=FCurFunc
end else
begin
FCurDelta:=FPosSideArg-FCurArg;
FPosSideArg:=FCurArg; FPosSideFunc:=FCurFunc
end;
FDeltaArg:=FPosSideArg-FNegSideArg;
CurRoot:=FCurArg;CurDelta:=abs(FCurDelta);
Result:=FCurFunc=0.0
end {FalsePos.DoIteration};
//Инициализация полей класса
procedure TNonLinearEqFalsePos.Initialize;
begin
if LeftSideFunc<0.0 then
begin
FNegSideArg:=LeftSideArg; FNegSideFunc:=LeftSideFunc;
FPosSideArg:=RightSideArg; FPosSideFunc:=RightSideFunc;
end else
begin
FNegSideArg:=RightSideArg; FNegSideFunc:=RightSideFunc;
FPosSideArg:=LeftSideArg; FPosSideFunc:=LeftSideFunc;
end;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
34
FDeltaArg:=FPosSideArg-FNegSideArg;
end {FalsePos.Initialize};
// Реализация методов класса TnonLinearEqRidders (метод Ридерса)
// Реализация алгоритма Ридерса
function TNonLinearEqRidders.DoIteration(var CurRoot,CurDelta:double):Boolean;
var
MidpointArgValue, MidpointFuncValue, Temp, NewArg:double; sgn:integer;
begin
MidpointArgValue:=0.5*(FLeftArg+FRightArg);
MidpointFuncValue:=LeftHandSide(MidpointArgValue);
Temp:=sqrt(Sqr(MidpointFuncValue)-FLeftFunc*FRightFunc);
Result:=Temp=0.0;
if Result then Exit;
if FLeftFunc>=FRightFunc then sgn:=1 else sgn:=-1;
NewArg:=MidpointArgValue+(MidpointArgValue-FLeftArg)*sgn*MidpointFuncValue/Temp;
Result:=abs(FCurArg-NewArg)<AbsAcc;
if Result then
begin
CurRoot:=FCurArg;
CurDelta:=abs(FCurArg-NewArg);
Exit;
end;
FCurArg:=NewArg;
FCurFunc:=LeftHandSide(FCurArg);
if (MidpointFuncValue<0.0) xor (FCurFunc<0.0) then
begin
FLeftArg:=MidpointArgValue; FLeftFunc:=MidpointFuncValue;
FRightArg:=FCurArg; FRightFunc:=FCurFunc
end else
if (FLeftFunc<0.0) xor (FCurFunc<0.0) then
begin
FRightArg:=FCurArg; FRightFunc:=FCurFunc
end else
if (FRightFunc<0.0) xor (FCurFunc<0.0) then
begin
FLeftArg:=FCurArg; FLeftFunc:=FCurFunc;
end;
CurRoot:=FCurArg; CurDelta:=abs(FRightArg-FLeftArg);
Result:=FCurFunc=0.0;
end {TNonLinearEqRidders.DoIteration};
// Инициализация полей класса
procedure TNonLinearEqRidders.Initialize;
begin
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
35
FLeftArg:=LeftSideArg;FRightArg:=RightSideArg;
FLeftFunc:=LeftSideFunc;FRightFunc:=RightSideFunc;
FCurArg:=MaxDouble;
end {TNonLinearEqRidders.Initialize};
Реализация методов классов Брента и Ньютона
// Реализация методов класса TnonLinearEqBrent (метод Брента)
// Реализация алгоритма Брента
function TNonLinearEqBrent.DoIteration(var CurRoot,CurDelta:double):Boolean;
var numerator,denominator,aFraction,bFraction,aMin,bMin,CurAcc,min,tempDelta:double;
begin
if FSecondPointFunc*FThirdPointFunc>0.0 then
begin
FThirdPointArg:=FFirstPointArg;FThirdPointFunc:=FFirstPointFunc;
FFirstDx:=FSecondPointArg-FFirstPointArg;FSecondDx:=FFirstDx
end;
if abs(FThirdPointFunc)<abs(FSecondPointFunc) then
begin
FFirstPointArg:=FSecondPointArg;
FSecondPointArg:=FThirdPointArg;FThirdPointArg:=FFirstPointArg;
FFirstPointFunc:=FSecondPointFunc;
FSecondPointFunc:=FThirdPointFunc;FThirdPointFunc:=FFirstPointFunc;
end;
tempDelta:=0.5*(FThirdPointArg-FSecondPointArg);
CurDelta:=abs(tempDelta);
CurAcc:=2.0*RelAcc*abs(FSecondPointArg)+0.5*AbsAcc;
if (CurDelta<=CurAcc) or (FSecondPointFunc=0.0) then
begin
CurRoot:=FSecondPointArg;
Result:=true; Exit
end;
if (abs(FSecondDx)>=CurAcc) and (abs(FFirstPointFunc)>abs(FSecondPointFunc)) then
begin
bFraction:=FSecondPointFunc/FFirstPointFunc;
if FFirstPointArg=FThirdPointArg then
begin
numerator:=2.0*tempDelta*bFraction;denominator:=1.0-bFraction
end else
begin
denominator:=FFirstPointFunc/FThirdPointFunc;
aFraction:=FSecondPointFunc/FThirdPointFunc;
numerator:=bFraction*(2.0*tempDelta*denominator*(denominator-aFraction)(FSecondPointArg-FFirstPointArg)*(aFraction-1.0));
denominator:=(denominator-1.0)*(aFraction-1.0)*(bFraction-1.0)
end;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
36
if numerator>0.0 then denominator:=-denominator;
numerator:=abs(numerator);
aMin:=3.0*tempDelta*denominator-abs(CurAcc*denominator);
bMin:=abs(FSecondDx*denominator);
if aMin<bMin then min:=aMin else min:=bMin;
if 2.0*numerator<min then
begin
FSecondDx:=FFirstDx; FFirstDx:=numerator/denominator
end else
begin
FFirstDx:=tempDelta; FSecondDx:=FFirstDx
end
end else
begin
FFirstDx:=tempDelta; FSecondDx:=FFirstDx
end;
FFirstPointArg:=FSecondPointArg; FFirstPointFunc:=FSecondPointFunc;
if abs(FFirstDx)>CurAcc then FSecondPointArg:=FSecondPointArg+FFirstDx
else
if tempDelta<0.0 then FSecondPointArg:=FSecondPointArg-CurAcc
else FSecondPointArg:=FSecondPointArg+CurAcc;
FSecondPointFunc:=FLeftHandSide(FSecondPointArg);
CurDelta:=abs(tempDelta); CurRoot:=FSecondPointArg;
Result:=FSecondPointFunc=0.0
end {TNonLinearEqBrent.DoIteration};
// Инициализация полей класса
procedure TNonLinearEqBrent.Initialize;
begin
FSecondPointArg:=RightSideArg; FSecondPointFunc:=RightSideFunc;
FThirdPointArg:=FSecondPointArg; FThirdPointFunc:=FSecondPointFunc;
FFirstPointArg:=LeftSideArg; FFirstPointFunc:=LeftSideFunc;
FFirstDx:=FSecondPointArg-FFirstPointArg;FSecondDx:=FFirstDx
end {TNonLinearEqBrent.Initialize};
// Реализация методов класса TNonLinearEqNewton (метод Ньютона-Рафсона)
// Реализация алгоритма Ньютона-Рафсона
function TNonLinearEqNewton.DoIteration(var CurRoot,CurDelta:double):Boolean;
// Описание локальной переменной в Delphi предшествует началу блока begin end
// и обозначается служебным словом var
// Область видимости локальной переменной ограничена телом метода.
// Вне метода DoIteration переменная tempArg недоступна
var tempArg:double;
begin
if (((FCurArg-FHighArg)*FCurDerivative-FCurFunc)*
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
37
((FCurArg-FLowArg)*FCurDerivative-FCurFunc)>0)
or
(abs(2.0*FCurFunc)>abs(FPrevDeltaArg*FCurDerivative)) then
begin
FPrevDeltaArg:=FDeltaArg;
FDeltaArg:=0.5*(FHighArg-FLowArg);
FCurArg:=FLowArg+FDeltaArg;
if FLowArg=FCurArg then
begin
CurRoot:=FCurArg; CurDelta:=abs(FDeltaArg);
Result:=true;Exit
end
end else
begin
FPrevDeltaArg:=FDeltaArg; FDeltaArg:=FCurFunc/FCurDerivative;
tempArg:=FCurArg; FCurArg:=FCurArg-FDeltaArg;
if FCurArg=tempArg then
begin
CurRoot:=FCurArg; CurDelta:=abs(FDeltaArg);
Result:=true;Exit
end
end;
CurDelta:=abs(FDeltaArg); CurRoot:=FCurArg;
Result:=CurDelta<2.0*RelAcc*abs(FCurArg)+0.5*AbsAcc;
if Result then Exit;
FCurFunc:=LeftHandSide(FCurArg);
FCurDerivative:=FDerivative(FCurArg);
if FCurFunc<0.0 then FLowArg:=FCurArg else FHighArg:=FCurArg;
Result:=FCurFunc=0.0
end {TNonLinearEqNewton.DoIteration};
// Инициализация полей класса
procedure TNonLinearEqNewton.Initialize;
begin
if LeftSideFunc<0.0 then
begin
FLowArg:=LeftSideArg; FHighArg:=RightSideArg;
end else
begin
FHighArg:=LeftSideArg; FLowArg:=RightSideArg;
end;
FCurArg:=Root;
FPrevDeltaArg:=Delta;FDeltaArg:=FPrevDeltaArg;
FCurFunc:=LeftHandSide(FCurArg);
FCurDerivative:=FDerivative(FCurArg);
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
38
end {TNonLinearEqNewton.Initialize};
//Метод GetRoot для метода Ньютона-Рафсона проверяет наличие производной функции f(x)
function TNonLinearEqNewton.GetRoot
(const aLeftSideArg, aRightSideArg:double): double;
begin
if not Assigned(FDerivative) then
// Создается объект исключительной ситуации
raise EArgumentNullException.Create('Производная левой части не задана!');
// Вызывается унаследованный метод GetRoot
Result:=inherited GetRoot (aLeftSideArg, aRightSideArg)
end {TNonLinearEqNewton.GetRoot};
// В данном модуле отсутствует секция инициализации
// Но ее место было бы здесь
// Модуль завершается служебным словом end с точкой в конце
end.
Окна среды Turbo Delphi Explorer
По умолчанию в среде Turbo Delphi Explorer есть
 главное окно, расположенное в верхней части экрана. Это самое узкое
окно, на котором располагается главное меню среды и кнопки быстрого
вызова, дублирующие некоторые команды меню. Набор кнопок может
меняться пользователем из меню View командой Toolbars и далее,
командой Customize…. Главным окном приложения называется то окно,
при закрытии которого закрываются все остальные окна.
 Пять дополнительных (вторичных окон), одно из которых находится в
центре экрана. Это окно по умолчанию открывает страницу Welcome
page. При работе над проектом в центральное окно добавляются
страницы с содержательной частью приложения и модулей.
 Слева и справа от центрального окна находятся по два небольших
симметрично расположенных окошка:
o Слева наверху - окно Structure, которое во время работы над
проектом будет содержать древесную структуру модуля,
находящегося в фокусе работы.
o Слева внизу – окно Object inspector будет при работе над оконным
приложением содержать свойства и события формы, находящейся
в фокусе. Вызовите контекстное меню на клиентской области окна
Object Inspector и выберите в нем команду Arrange -> by Name.
o Справа наверху – окно Project Manager будет содержать древесную
структуру группы проектов, над одним из которых ведется работа.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
39
o Справа внизу - окно Tool Palette содержит в зависимости от
контекста либо палитру шаблонов проектов, которые можно
создавать в среде, либо набор компонент, которые можно
использовать
при
проектировании
интерфейса
оконного
приложения.
Вопросы для самоконтроля
Интерфейсная секция vs секция реализации в Delphi.
Смысл директивы uses в Delphi.
Синтаксис описания постоянных в Delphi.
Синтаксис описания типов в Delphi.
Смысл служебного слова class of в Delphi.
Смысл служебного слова of object в Delphi.
Какую роль играет параметр sender в обработчиках в Delphi?
Порядок описания типов, полей и методов в Delphi.
Смысл модификатора const в описании параметров подпрограмм в
Delphi.
10.Синтаксис описания класса в Delphi.
11.Синтаксис описания полей, методов и свойств в Delphi.
12.Синтаксис описания виртуальных методов в Delphi.
13.Абстрактный класс и абстрактный метод.
14.Конструктор в Delphi. Класс TObject.
15.Освобождение объектов в Delphi.
16.Классы исключительных ситуаций в Delphi.
17.Вызов метода предка в Delphi.
18.Что делает функция Assigned?
19.Как создается объект исключительной ситуации в Delphi?
20.Когда вызываются методы типа set?
21.Что означает модификатор var в списке параметров подпрограммы в
Delphi?
22.Что означает идентификатор Result в теле функции в Delphi?
23.Опишите окна среды Turbo Delphi Explorer.
1.
2.
3.
4.
5.
6.
7.
8.
9.
Консольное приложение в Delphi
В этом модуле в среде Turbo Delphi строится консольное приложение,
тестирующее работу классов решения нелинейного уравнения.
Создадим в среде Turbo Delphi консольное приложение, тестирующее работу
классов решения нелинейного уравнения.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
40
Характерной особенностью консольных приложений является то, что такие
приложения открывают одно окно, через которое пользователь общается с
приложением. Фон этого окна черный, поэтому консольные приложения
называют еще «приложениями в черном окне». Это наиболее простой тип
приложений, характерный для прежней операционной системы DOS, не
отягощенной графическим интерфейсом Windows.
Прежде всего, создайте «проектную группу», или Projects Group. Проектная
группа является логическим контейнером. В такую группу обычно собирают
приложения, объединенные одной общей идеей. В нашем случае это будут
приложения, тестирующие классы решения нелинейного уравнения.
Откройте среду Turbo Delphi. В древесной структуре в окне Tool Palette найдите
и откройте узел Other Files. В списке этого узла выберите команду Projects
Group и дважды щелкните над этим элементом. В окне Project Manager,
расположенном непосредственно над окном Tool Palette, появится узел с
именем Project Group 1. Щелкнув правой кнопкой над этим именем, сохраним
группу под новым именем nonLinEqTestGroup с помощью команды Save Project
Group As…. При сохранении среда предложит по умолчанию каталог Borland
Studio Projects, открытый в My Documents Вашего профиля. Можно создать
внутри каталога Borland Studio Projects рабочий каталог с именем
nonLinEqTestProjects и в него сохранить проектную группу. Создать проектную
группу, да и любой проект можно так же через команды New главного меню
File среды.
Добавьте к проектной группе nonLinEqTestGroup консольное приложение. Для
этого в окне Project Manager над именем проектной группы щелкните правой
кнопкой и выберем команду Add New Project…. Появится окно New Items. В
этом окне перечислены возможные типы приложений, которые могут быть
созданы в среде. Это так называемые «шаблоны приложений», или templates.
Выберите из списка шаблонов Consol Application, дважды щелкнув мышкой.
Среда создаст файл шаблона консольного приложения со следующим
содержанием
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
begin
{TODO -oUser -cConsole Main : Insert code here }
end.
Приложение в Delphi начинается служебным словом program со следующим за
ним именем – произвольным идентификатором.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
41
Директива компилятору {$APPTYPE CONSOLE} указывает на то, что
программа Project1 должна рассматриваться как консольное приложение. В
списке uses среда ссылается на библиотечный модуль SysUtils, хотя
подпрограммы из этого модуля могут и не использоваться в приложении.
To-Do List
Комментарий {TODO -oUser -cConsole Main: Insert code here} в теле программы
указывает на место, где пользователь должен поместить свой код. В данном
случае комментарий имеет особую форму. Он начинается словом TODO и имеет
особые символы -o и -c. Такой комментарий готовит обычно программистсупервизор, возглавляющий группу других программистов, работающих над
одним проектом. Супервизор открывает список того, что следует сделать (ToDo List), добавляя в него новые элементы или отмечая те, которые выполнены.
Выберите команду To-Do List из меню View. Вот, что Вы увидите
Через контекстное меню списка можно добавлять элементы списка,
редактировать содержание его полей. Командой ToDo List из меню View
элемент списка добавляется в качестве комментария к тексту программы.
Можно отмечать факт выполнения сделанной работы, щелкая на
прямоугольник, или CheckBox, слева в строке списка. Сделайте это и
посмотрите результат. Строка комментария должна измениться и принять вид
{ DONE -oUser -cConsole Main : Insert code here }
Закроем окно To-Do List.
Создание консольного приложения в Delphi
 Занесите модуль uNonLinearEquation.pas с классами решения нелинейного
уравнения, текст которого изучался выше, в каталог nonLinEqTestProjects.
 В окне Project Manager измените имя проекта по умолчанию Project1, выбрав
из контекстного меню (правая кнопка мышки над именем проекта) команду
Rename. Дайте проекту говорящее имя cnslNonLinEqTest и сохраните его.
 Выберите проект cnslNonLinEqTest в окне Project Manager. В контекстном
меню выберите команду Add… и добавьте к проекту модуль
uNonLinearEquation.pas. В список uses проекта cnslNonLinEqTest должна
добавиться ссылка на этот модуль вида
uNonLinearEquation in 'uNonLinearEquation.pas';
При этом сам модуль откроется в отдельном окне редактора.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
42
Ниже будет дано описание класса nonLinEqTest, которое разместится перед
служебным словом begin в той части кода приложения, которая называется
секцией описания.
В тело проекта (после строки с комментарием {DONE …}) поместите следующий
код
nlTest:=nonLinEqTest.Create;
while nlTest.JustDoIt do;
nlTest.Free;
 Первый оператор создает экземпляр (объект) nlTest класса nonLinEqTest.
 Второй оператор является оператором цикла. В условии цикла находится
обращение к методу JustDoIt объекта nlTest класса nonLinEqTest. Цикл
прерывается, когда метод JustDoIt возвратит значение false.
 В заключении стоит оператор вызова метода Free, освобождающего
память от объекта nlTest.
Теперь займитесь кодом класса nonLinEqTest.
В начале добавьте в список uses ссылку на два модуля Dialogs и Math, которые
используются в методах класса nonLinEqTest.
Далее наберите код секции описания следующего содержания.
// Описан массив ссылок на типы классов (массив метаклассов)
// решения нелинейных урванений.
// Это массив из 6 элементов типа TnonLinearEqClass.
// Обратите внимание на синтаксис описания массива постоянных
const
nonLinEqClasses:array [0..5] of TnonLinearEqClass =
(TNonLinearEqBisection, TNonLinearEqSecant, TNonLinearEqFalsePos,
TNonLinearEqRidders, TNonLinearEqBrent, TNonLinearEqNewton);
type
nonLinEqTest=class
private
// хранит текущий объект решения нелинейного уравнения
nleq:TNonLinearEquation;
// возвращает левую часть нелинейного уравнения
function f(x:double):double;
// возвращает производную левой части нелинейного уравнения
function fder(x:double):double;
public
// хранит текущий индекс нелинейного уравнения
item: integer;
// хранит текущую итерацию цикла решения нелинейного уравнения
itr: integer;
// метод, используемый как обработчик на каждом шаге итерации
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
43
function onIter(sender:TObject):Boolean;
// метод, используемый как обработчик перед началом итерации
procedure beforeIter(sender:TObject);
// метод, используемый как обработчик после окончания итерации
procedure afterIter(sender:TObject);
// конструктор класса
constructor Create;
// метод, выбирающий уравнение и метод решения, и возвращающий результат
function JustDoIt:boolean;
end;
Содержание методов класса NonLinEqTest
// Функция левой части уравнения
function nonLinEqTest.f(x:double):double;
begin
// Если поле item не равно 0 или 1, то вызов метода не имеет смысла.
// В этом случае метод возвращает NaN.
// NaN - библиотечная постоянная, означающая, что число не определено (Not-a-Number), а
// имеет значение 0.0/0.0.
// Результат NaN позволяет контролировать «осмысленность» кода.
result:=NaN;
case item of
0:result:=sin(x);
1:
begin
if (x>1.0) or (x=0.0) then
// Порождается объект исключительной ситуации, если аргумент
// выходит из области определения функции
raise EArgumentOutOfRangeException.Create
('Значение аргумента выходит из области определения!');
result:=tan(x)-sqrt(1.0-x*x)/x;
end;
end;
end {};
function nonLinEqTest.fder(x:double):double;
begin
result:=NaN;
case item of
0:result:=cos(x);
1:
begin
if (x>=1.0) or (x=0.0) then
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
44
raise EArgumentOutOfRangeException.Create
('Значение аргумента выходит из области определения!');
result:=1.0/Sqr(cos(x))+1.0/sqrt(1.0-Sqr(x))/Sqr(x)
end
end;
end {};
constructor nonLinEqTest.Create;
begin
// Вызов библиотечной процедуры, инициализирующей генератор
// последовательности псевдослучайных чисел по данным компьютерного таймера
Randomize;
end {};
function nonLinEqTest.onIter(sender:TObject):Boolean;
begin
// На каждом шаге итерационного цикла обработчик увеличивает
// на единицу поле-счетчик itr с помощью стандартной процедуры Inc
Inc(itr);
// Обработчик в данном примере возвращает всегда false,
// т.е. не прерывает итерационный цикл ни при каком условии
result:=false;
end {};
procedure nonLinEqTest.beforeIter(sender:TObject);
begin
// Перед началом итерационного цикла счетчик итераций обнуляется
itr:=0;
end {};
procedure nonLinEqTest.afterIter(sender:TObject);
begin
// По окончании итерационного цикла значение поля-счетчика итераций itr
// выводится на экран с помощью стандартной процедуры writeln
// Стандартная функция IntToStr преобразует целое число в строку
writeln('itr='+IntToStr(itr));
end {};
function nonLinEqTest.JustDoIt:boolean;
// стандартный способ описания локальных переменных в Delphi начинается
// служебным словом var и располагается сразу за именем метода, перед началом
// служебного слова begin
// Здесь описаны локальные переменные двух типов char (символ) и double.
// После завершения выполнения метода
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
45
// значения локальных переменных являются неопределенными
var ch:char;
leftSide, rightSide:double;
begin
// Создается экземпляр (объект) класса решения нелинейных уравнений.
// Тип класса выбирается случайным образом из массива шести постоянных типов
// nonLinEqClasses, описаного в начале программы.
// Стандартная функция Random(n) возвращает целое случайное число, лежащее
// в интервале [0; n-1].
// Одновременно вызывается конструктор класса Create.
// Ссылка на созданный объект помещается в поле nleq
nleq:= nonLinEqClasses[Random(6)].Create;
// Поле item выбирается случайным образом из чисел 0 и 1.
// Оно определяет левую часть решаемого уравнения.
item:= Random(2);
// Вызваются свойства LeftHandSide, OnBeforeIterations, OnAfterIterations и OnIteration
// объекта nleq, которые присваивают необходимые значения соответствующим полям.
// Специфичный для Delphi оператор with позволяет сократить запись имен членов класса,
// объединив под именем объекта nleq все операторы, стоящие между begin и end.
// Все работает так, как если бы свойства вызывались под полным именем (через точку)
// nleq.LeftHandSide, nleq.OnBeforeIterations и т.д.
// В языках C нет операторов типа with, хотя есть возможности сокращать запись имен
with nleq do
begin
LeftHandSide:=f;
OnBeforeIterations:=beforeIter;
OnAfterIterations:=afterIter;
OnIteration:=onIter;
end;
// Оператор is сравнивает тип данного класса с типом класса конкретного объекта.
// В данном случае определяется,
// является ли nleq объектом класса TNonLinearEqNewton, или его наследников.
// Это необходимо для инициализации поля, ссылающегося на метод,
// содержащий производную функции, стоящей в левой части уравнения.
// Такое поле есть только у классов типа TnonLinearEqNewton.
// Тот же оператор is есть в языке C#, но отсутствует в языке C++.
if nleq is TNonLinearEqNewton then
// Если классом объекта nleq является TnonLinearEqNewton, то
// у этого объекта есть свойство Derivative, которое устанавливает ссылку на
// метод-производную.
// Т.к. поле nleq имеет тип абстрактного класса TNonLinearEquation,
// то необходимо преобразовать ссылку на объект типа TNonLinearEquation,
// у которого нет свойства Derivative, к ссылке на объект типа TnonLinearEqNewton,
// где это поле есть.
// Такое преобразование типов делает оператор as.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
46
// Оператор as есть также в языке C#, но его нет в языке C++.
(nleq as TNonLinearEqNewton).Derivative:=fder;
// Стандартная функция writeln выводит на экран (в черное окно) текущее имя класса и
// переносит каретку на начало новой строки.
// Имя это возвращается методом-функцией ClassName, который
// унаследован классом TNonLinearEquation (и его наследниками) у
// своего непосредственного предка – библиотечного класса TObject.
writeln ('Class name: '+nleq.ClassName);
write ('Equation: ');
// Здесь выводится строка с выражением левой части решаемого уравнения
// в зависимости от случайно заданного значения поля item.
if item=0 then writeln('sin(x) = 0')
else writeln('tg(x)-sqrt(1.0-x*x)/x = 0');
// Операторы, с помощью которых пользователь вводит левую и правую границу
// интервала изоляции корня с помощью клавиатуры
write('Enter left side: ');readln(leftSide);
write('Enter right side: ');readln(rightSide);
// Операторы try…except позволяют создать осмысленную реакцию
// на, возможно, возникшую в ходе выполнения кода исключительную ситуацию.
// Если во время выполнения кода, находящегося между try и except, возникает ИС,
// то управление передается в область, следующую за except, а объект
// исключительной ситуации помещается в параметр E, стоящий в теле except.
// В данном случае предполагается, что объект ИС может быть порожден методом
// GetRoot объекта nleq. Тогда управление будет передано стандартной процедуре
// ShowMessage (из модуля Dialogs). Параметром этой процедуры является строка,
// а ее цель – вывод этой строки на экран в форме окна сообщения.
// В данном случае фактическим параметром является свойство Message объекта
// исключительной ситуации E, порожденного внутри метода GetRoot.
// Значение этого свойства задавалось при создании объектов ИС в коде метода GetRoot.
// Если ИС не возникла, то на экран выводится найденное значение корня уравнения
try
writeln ('root=', nleq.GetRoot(leftSide,rightSide));
except on E:Exception do
ShowMessage (E.Message);
end;
// На экран выводится сообщение, приглашающее либо возобновить тестирование,
// либо завершить его
write ('Enter any key to continue or x - to end');
readln (ch);
// функция возвращает либо true, если введенный символ не совпадает с ‘x’,
// либо false при совпадении.
// Вернитесь к телу программы, убедившись в осмысленности написанного там кода
result:= ch<>'x';
end {};
// Описание глобальной переменной программы, содержащей ссылку
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
47
// на объект nlTest класса nonLinEqTest.
var nlTest:nonLinEqTest;
Полный текст модуля консольного приложения можно найти по ссылке.
В рассмотренном примере тестирующего приложения не менялись погрешности
вычисления корня (свойства RelAcc и AbsAcc) во всех вариантах счета. Их
значения оставались заданными по умолчанию.
В качестве упражнения внесите в написанный код изменения, которые
позволили бы менять погрешности вычислений. Проверьте работу этого кода.
Вопросы для самокотроля
1. Что характерно для консольного приложения и как создать его шаблон в
среде?
2. Какую роль играет «проектная группа» в создании проектов и как ее
создать в среде Turbo Delphi?
3. Каков синтаксис директивы компилятору в Delphi?
4. Какую роль играет комментарий TODO и что такое ToDo List?
5. Какую роль в списке uses в Delphi играет служебное слово in?
6. Как создается экземпляр класса в Delphi и как он освобождается (на
примере объекта nlTest, описанного в тексте)?
7. Синтаксис описания массива постоянных в Delphi.
8. Какой смысл имеет константа NaN?
9. Что делает процедура Randomize в Delphi?
10.Смсыл процедуры Inc в Delphi.
11.Смысл стандартной функции IntToStr.
12.Что делает стандартная функция Random?
13.Смысл оператора with в Delphi.
14.Смысл оператора is.
15.Смысл оператора as.
16.Преобразование типов в Delphi. Для чего оно делается в коде, описанном
выше?
17.Смсыл метода ClassName класса TObject.
18.Синтаксис и смысл операторов try… except в Delphi.
19.Смысл стандартной процедуры ShowMessage в Delphi.
Оконное приложение
В этом модуле в среде Delphi строится оконное приложение, тестирующее
классы решения нелинейного уравнения. Разрабатывается дизайн приложения.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
48
Добавьте в группу проектов nonLinEqTestGroup оконное приложение. Для этого
в окне Project Manager на узле щелкните правой кнопкой мышки и выберите
команду Add New Project…. Появится окно New Items. В этом окне
перечислены возможные типы приложений, которые могут быть созданы в
среде. Выберем узел Delphi Projects на левой панели окна. В появившихся
шаблонах, расположенных на правой панели, выберите шаблон VCL Forms
Application.
Аббревиатура VCL означает Visual Component Library. Это библиотека классов,
на базе которой будет создаваться проект. Одним из классов библиотеки VCL
является класс TForm. Объекты класса TForm являются основными объектами
оконного приложения – они и есть окна.
После добавления проекта среда откроет несколько файлов.
 В окне Project Manager появится узел Project1.exe. Изменим его имя на
wNonLinEqTest правой кнопкой мышки и командой Rename. Изменить
имя проекта можно так же командой Save Project As… из главного меню
File среды.
 В узле ниже есть еще два файла с общим именем unit1, но разными
расширениями pas и dfm. Это шаблоны файлов, содержащих код (файл
.pas) и текстовой файл данных для объекта класса формы (файл .dfm –
delphi form), созданных средой по умолчанию. Измените имя unit1 на
fNonLinEqTest.
После этих изменений сохраните файлы нового проекта в папку имеющейся
группы nonLinEqTestProjects командой Save из меню File.
Файл программы оконного приложения в Delphi
В меню Project среды выберите команду View Source. Среда покажет
содержание созданного ею файла новой программы вида
program wNonLinEqTest;
uses
Forms,
fNonLinEqTest in 'fNonLinEqTest.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
49
Программа wNonLinEqTest имеет обычную для Delphi структуру. А именно, она
содержит
 Заголовок программы program wNonLinEqTest;
 Список используемых модулей uses
Forms, // Модуль Forms является библиотечным модулем среды
fNonLinEqTest in 'fNonLinEqTest.pas' {Form1};
// в списке модулей специально указывается, где находится модуль fNonLinEqTest.
// Комментарий {Form1} подсказывает, что в этом модуле находится описание класса
// TForm1 – наследника TForm.
// Класс TForm1 создан средой как шаблон для нашего редактирования.
 Далее находится так называемая директива компилятору вида {$R *.res}.
Любая директива компилятору в Delphi это команда, указывающая на
необходимость учитывать некоторые опции при компиляции кода.
Директива заключена в фигурные скобки (как комментарий) и начинается
знаком $. За знаком $ следуют поля директивы, которые определяют ее
содержание. В данном случае поля директивы R *.res указывают на то, что
к исполняющему файлу программы (файлу wNonLinEqTest.exe) должен быть
подшит файл ресурсов, имеющий расширение .res и носящий то же имя,
что файл программы wNonLinEqTest. В файле ресурсов размещена версия
приложения и иконка (значок), которым будет сопровождаться
исполняющий файл wNonLinEqTest.exe в OS Windows. При желании
программист может изменить значок. Для этого в меню Project есть
команда Options…. В окне, открывающемся по этой команде, в пункте
Application можно найти кнопку загрузчика значка (Load Icon…).
 Далее расположено тело программы, окруженное двумя служебными
словами begin и end. (с точкой на конце). В теле находятся три
оператора вызова трех методов объекта Application. Объект Application
создан в одной из инициализирующих секций модулей библиотеки среды,
которые вызываются при компиляции и связывании с модулем Forms из
списка uses файла проекта. Он является объектом класса TApplication,
доступным нашему приложению.
o Метод Initialize объекта Application в общем случае предусмотрен для
активизации
дополнительного
инициализирующего
кода,
помещенного
в
процедуру
без
параметров.
Адрес
инициализирующей процедуры должен быть передан стандартной
переменной InitProc типа указатель (pointer). Например, можно в
каком-либо модуле, либо в самом проекте описать процедуру без
параметров myInit, поместив в нее любой код. Затем, в
инициализирующей секции этого модуля или в начале самой
программы написать оператор InitProc:=@myInit. Процедура myInit
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
50
будет вызвана методом Application.Initialize. В данном случае такого
кода нет, и метод Initialize пуст. Знак @ определяет в Delphi адрес
подпрограммы (в данном случае адрес процедуры myInit).
o Метод CreateForm(TForm1, Form1) создает экземпляр (объект) Form1
класса TForm1. Эти имена классу и объекту дала среда. После смены
имени объекта Form1, который будет проведен ниже, среда изменит
оба имени в данном файле. Метод CreateForm это не конструктор
класса TForm1, а лишь метод класса TApplication, внутри которого
вызывается конструктор класса TForm1. Первый параметр метода
CreateForm принимает ссылку на тип класса (в данном случае
TForm1). Второй параметр возвращает ссылку на созданный
методом объект (в данном случае Form1) класса, указанного
первым параметром.
o Метод Run объекта Application образует цикл опроса сообщений
(событий), внутри которого постоянно «крутится» управление
программой. После завершения метода Run работа программы
заканчивается, и выполняются все завершающие секции модулей,
перечисленных в списке uses в обратном порядке. Метод Run
завершается после того, как будет закрыто главное окно
приложения. В данном случае окно одно и оно главное.
Модуль формы
Файл модуля формы fNonLinEqTest.pas, созданный средой и названный нами
fNonLinEqTest, содержит по умолчанию описание класса TForm1. Откройте текст
этого файла, дважды щелкнув по узлу fNonLinEqTest.pas в окне Project Manager. В
открытом окне есть три страницы, помеченные внизу ярлычками Code, Design и
History. На ярлычке Design дается изображение окна, рабочая область которого
выглядит пустой строительной площадкой, размеченной под строительство.
Щелкнув по ярлычку Code, получите изображение кода модуля формы. Вот
текст этого кода
unit fNonLinEqTest;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
51
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
end.
Модуль имеет стандартную структуру.
 В списке uses интерфейсной секции приведено довольно много
стандартных библиотечных модулей, не все из которых обычно
используются при написании кода. Среда дает этот список «про запас».
На объем конечного кода это не влияет.
 В разделе type дается «скелет» описания класса TForm1 – наследника
библиотечного класса TForm. Новый класс пуст. Размечены области
описания членов класса с разным уровнем доступа.
 Последним в интерфейсной секции приводится описание экземпляра
(объекта) Form1 как объекта класса TForm1.
 В секции реализации находится директива компилятору {$R *.dfm}. Эта
директива требует подшить в код приложения файл с тем же именем
fNonLinEqTest, но с расширением .dfm. Файл fNonLinEqTest.dfm содержит
важные сведения о свойствах формы, которые накапливаются и
изменяются в ходе визуального проектирования.
Содержание большинства файлов, созданых средой, поддерживается
автоматически инструментами среды. Это, в частности, относится к файлу
программы wNonLinEqTest.dpr, файлу ресурсов приложения wNonLinEqTest.res и
файлу формы fNonLinEqTest.dfm. Не рекомендуется без особых на то причин
редактировать содержание этих файлов напрямую, т.к. за их содержание
отвечают инструменты среды. Но сохранять эти файлы необходимо.
Присоедините к проекту модуль с классами решения нелинейных уравнений
uNonLinearEquation. Для этого в окне Project Manager щелкните правой кнопкой по
имени проекта и выберите команду Add…. В открывшемся окне выберите
модуль uNonLinearEquation. После этого добавьте в список uses модуля
fNonLinEqTest формы ссылку на модуль классов uNonLinearEquation.
При создании интерфейса окна используется в основном два окна среды – Tool
Palette (палитра инструментов) справа внизу и Object Inspector (инспектор
объектов), находящийся слева внизу экрана. В палитре инструментов находятся
ссылки на классы, объекты которых требуется разместить в окне. В инспекторе
объектов отображаются свойства и обработчики событий этих объектов. Имена
объектов, связанных с приложением, помещены в список, открывающийся в
верхней части окна Object Inspector. В окне Structure изображается древесная
структура модуля, находящегося в данный момент окне редактора.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
52
Создание дизайна окна
В начале измените свойства самой формы в инспекторе объектов
 Щелкните по ярлычку Design окна формы, либо дважды по имени
fNonLinEqTest.dfm в окне Project Manager. На странице Properties (свойства)
инспектора объектов найдите в левом столбце свойство Name и в правой
колонке замените стоящую там строку Form1 на NonLinearEqTest. Так будет
называться наша форма. Нажмите Enter. Обратите внимание, что среда
автоматически изменила имя класса на TNonLinearEqTest. Изменения
произошли не только в модуле fNonLinEqTest.pas, но и в файле программы
wNonLinEqTest.dpr (откройте этот файл из меню Project командой View
Source). Изменился так же и файл формы fNonLinEqTest.dfm. Посмотреть
этот файл можно, щелкнув правой кнопкой по изображению окна на
странице Design формы fNonLinEqTest и выбрав команду View As Text. Для
возврата к изображению окна так же через правую кнопку выберите
команду View As Form.
 В инспекторе объектов найдите имя Caption. Это свойство окна содержит
строку, изображаемую в заголовке окна. Его значение изменилось
автоматически с изменением имени окна. Обратите внимание, что
некоторые свойства изображены в инспекторе объектов полужирным
шрифтом. Эти свойства помещены в файл fNonLinEqTest.dfm. По мере
проектирования новые свойства будут меняться, и их значения будут
помещаться в файл fNonLinEqTest.dfm и изображаться полужирным
шрифтом в инспекторе объектов. Пока что изменены лишь свойства Name
и Caption. Увеличте размеры окна, растянув его вправо и вниз.
Автоматически изменятся значения свойств Width, Height в инспекторе
объектов и в файле fNonLinEqTest.dfm. Кроме того, изменятся свойства
ClientHeight и ClientWidth, которые определяют размеры клиентской
области окна. Клиентской (или рабочей) областью называется
поверхность окна, не включающая в себя заголовок окна и его границы.
На рабочей области можно размещать управляющие элементы
интерфейса, или, сокращенно, «кóнтролы» с ударением на первый слог,
или компоненты.
Дизайн компонент окна
Все контролы находятся в окне Tool Palette. Большая часть из тех, что будут
использоваться, расположены в разделе Standart.
 В разделе Standart найдите компоненту класса TGroupBox и щелкните на
ней, а затем на клиентской области формы. На площадке появится
изображение контрола с именем GroupBox1. Компонента GroupBox1 будет
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
53
использоваться как группирующая компонента, на которой будут
размещаться контролы поменьше для ввода информации в приложение. В
инспекторе объектов измените его имя (свойство Name) на GroupBoxIn, а
значение свойства Caption на In. Немного увеличьте его размеры. Обратите
внимание, что в коде формы произошли некоторые изменения (щелкните
по ярлычку Code). А именно, в списке членов класса TNonLinearEqTest
непосредственно за заголовком класса появилось описание поля
GroupBoxIn: TGroupBox;. Поле GroupBoxIn хранит ссылку на объект класса
TGroupBox, который был помещен на форму. Перед описанием нового
поля отсутствует какой-либо модификатор доступа типа private и
т.п. В Delphi члены класса имеют по умолчанию доступ published, т.е.
доступный для инспектора объектов и дизайнера среды. Сохраните
новую редакцию кода командой Save из меню File. Не забывайте делать
это после некоторого числа изменений кода, чтобы избежать потери
редакции при внезапном выключении компьютера.
 Вернувшись на страницу дизайнера Design, перенесите на форму еще
один объект класса TGroupBox из палитры компонент. Назовите новый
контрол GroupBoxOut, а его свойству Caption дайте значение Out.
Группирующая компонента GroupBoxOut будет площадкой для контролов,
управляющих выводом результатов на экран. Расширьте границы
GroupBoxOut, расположив его достаточно симметрично в окне по
отношению к GoupBoxIn.
 Группирующие компоненты используются как общие панели, несущие на
себе другие компоненты, объединенные логически и программно для
решения некоторой задачи интерфейса окна. У всех компонент есть
свойство Visible. Если у группирующей компоненты изменить Visible на
false, то все дочерние компоненты вместе с группирующими их
контролами станут невидимыми на экране. При этом свойства Visible
дочерних компонент могут оставаться равными true. К группирующим
компонентам (или, как их еще называют, «контейнерам») относятся,
конечно, объекты класса TForm. Контейнерами являются так же объекты
классов TFrame, TPanel (раздел Standart палитры), классов TScrollBox,
TFlowPanel и TGridPanel (раздел Additional). Класс TFrame порождает
объекты, подобные форме, но размещаются эти объекты на форме.
Объекты класса TPanel похожи на объекты класса TGroupBox, только в них
отсутствует окаймляющий контур с надписью, а надпись формируется по
центру панели. Контейнеры класса TScrollBox удобны для размещения
большого количества контролов, доступ к которым обеспечивается
полосами прокрутки (скроллинг). Объекты классов TFlowPanel и TGridPanel
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
54
отличаются от TPanel тем, что компоненты в этих контейнерах
помещаются в определенные фиксированные места. В объектах TGridPanel
эти места даже имеют номер столбца и строки, как в таблице (grid).
 Наполните GroupBoxIn компонентами из окна палитры компонент Tool
Palette, которые понадобятся для ввода информации в приложение:
 «Метка», или компонента типа TLabel. Поместите ее в верхнюю левую
часть GroupBoxIn. Назовите LabelMethod, а Caption дайте значение Method.
 Вторая метка типа TLabel. Поместите ее в верхнюю правую часть
GroupBoxIn. Назовите LabelFunc, а свойству Caption дайте значение Функция.
Объекты класса TLabel используются в интерфейсе обычно в качестве текстовых
меток, помещаемых вблизи другой компоненты и поясняющих ее функции. В
разделе Additional есть класс TStaticText, подобный по своим свойствам TLabel.
Отличие в основном в том, что объектами типа TStaticText можно управлять
непосредственно с клавиатуры – они имеют, как говорят, фокус ввода, являясь в
этом смысле полноценным окном. Объекты класса TLabel могут реагировать на
щелчок мышки и подобные события, но не имеют фокуса ввода. В большинстве
случаев это качество не требуется от меток.
 Под левой меткой Method поместите компоненту типа TComboBox из
палитры. Назовите ComboBoxMethod. В нем будет располагаться список
методов решения нелинейного уравнения. Найдите свойтсво Items и
щелкните справа по кнопке с тремя точками. Должно открыться окно
String List Editor, в поле которого наберите список методов решения
уравнения следующего содержания
Bisection
Secant
False Position
Ridders'
Brent's
Newton's
 Этот список должен появляться при щелчке по правой кнопке контрола
ComboBoxMethod. Свойству ItemIndex дайте значение 0 вместо прежнего
значения -1. В изображении компонента должно появиться слово Bisection
– первая строка списка. Свойство Style в инспекторе объектов измените на
csDropDownList. Свойство Style определяет, как будет изображаться
GroupBoxMethod при щелчке по его кнопке. По умолчанию это свойство
имеет значение csDropDown. Значение csDropDown позволяет при щелчке по
кнопке объекта появляться списку возможных значений, заготовленных
программистом, и редактировать вручную выбранное значение. Наш
список редактировать не имеет смысла – это название методов решения
уравнения. Поэтому свойство Style устанавливается в csDropDownList, при
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
55
котором редакция элементов списка запрещена. Существуют иные
значения свойства Style, которые принадлежат перечислимому типу
TComboBoxStyle. В частности, значение csSymple изображает список
статически по высоте компоненты.
 Еще один объект класса TComboBox поместите симметрично под меткой
Функция. Назовите его ComboBoxFunction. В нем будет список функций
нелинейных уравнений. Внесите в свойство Items список
sinx
tg(x)-x/sqrt(1-x^2)
 Измените свойство ItemIndex на 0 и Style на csDropDownList.
Объекты класса TComboBox предназначены для выбора и редактирования
вводимых в приложение значений. К подобным объектам относятся окошки
типа TEdit, которые, однако, не содержат списка выбора. Объекты класса
TListBox содержат список выбора, но весь список, или его значительная часть,
стационарно присутствуют на экране. В разделе Win32 палитры компонент есть
компонента TComboBoxEx, которая имеет больше возможностей для изображения
самого списка, чем объекты типа TComboBox.
 В окне Tool Palette, в разделе Additional выберите компоненту
TLabeledEdit. Перенесите ее на форму в GroupBoxIn и поместите ниже
контрола GroupBoxMethod. Назовите LEditLeft. В нем будет помещаться
число – левая граница интервала изоляции корня. Измените свойство
LabelPosition на lpLeft. Метка, поясняющая смысл редакционного окошка
должна переместиться влево. В инспекторе объектов выберите свойство
EditLabel и расширьте его, щелкнув по кнопке с плюсом слева. В
появившихся полях найдите свойство метки Caption и дайте ему значение
LeftSide=. В инспекторе объектов найдите свойство Hint и впишите в нем
строку Левая граница должна быть меньше правой!. Свойство ShowHint
установите в true. Это позволит выводить на экран предупреждающее
окошко, когда пользователь наведет указатель мышки на контрол
LEditLeft. Установите свойству Tag значение 1. Это свойство нумерует
окошки ввода и будет использовано в коде проекта.
 Такую же компоненту типа TLabeledEdit поместите чуть правее, на одной
высоте с предыдущей. Назовите ее LEditRight. Через него будет вводиться
число – правая часть интервала изоляции корня. Аналогично
предыдущему контролу поместите метку слева, изменив свойство
LabelPosition на lpLeft. Свойству Caption метки дайте значение RightSide=. В
свойство Hint вбейте строку Правая граница должна быть больше левой!.
Свойство ShowHint установите в true. Установите свойству Tag значение 2.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
56
Компоненты классов TLabeledEdit и TEdit предназначены для ввода и вывода
ограниченной информации в форме отдельного числа или короткой строки.
Компонента TLabeledEdit отличается от более простой компоненты TEdit
наличием связанной с ней метки, которая обычно сопровождает редакционное
окошко. В разделе Additional есть компонента TMaskEdit, объекты которой
позволяют задавать определенную маску для вводимого текста.
 Поместите на GroupBoxIn новую компоненту типа TGroupBox в левой части,
ниже LEditLeft. В этой компоненте будут расположены редакционные
окошки, в которых будут задаваться погрешности решения нелинейного
уранения. Поэтому назовите ее GroupBoxAcc, а свойству Caption дайте
значение Погрешности.
 Внутрь GroupBoxAcc поместите два редакционных окошка типа TLabeledEdit.
Назовите их LEditAbsAcc и LEditRelAcc соответственно, а свойствам Caption
их меток дайте значения Абсолютная и Относительная. Через эти окошки
будут вводиться абсолютная и относительная погрешности. Установите
свойству Tag этих компонент значения 3 и 4 соответственно. Установите
свойство ShowHint у обеих кошек в значение true. Но свойство Hint оставьте
пустым. Его значение будет задаваться в программе динамически.
 Внутрь GroupBoxIn, но вне GroupBoxAcc, справа от последнего поместите
кнопку (объект типа TButton). Назовите ее ButtonCompute, а свойству Caption
дайте значение Считай. Эта кнопка будет инициировать процесс счета.
Кнопки являются одним из наиболее распространенных управляющих
элементов интерфейса. Они управляют ходом приложения с помощью
односложного приема – «клика». Кроме обычной кнопки типа TButton, в палитре
инструментов в разделе Additional есть кнопки типа TВitBtn и TSpeedButton. На
поверхность объектов TВitBtn можно помещать значок (иконку). Изображение на
поверхности кнопки типа TSpeedButton можно ставить в зависимость от ее
состояния (кнопка нажата, отпущена, неактивна и т. п.).
Вот как, примерно, может выглядеть GroupBoxIn после проведенных редакций
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
57
Теперь необходимо заселить GroupBoxOut.
Поместите на него 4 компоненты типа TLabeledEdit в два столбца по паре и
назовите их LEditRoot, LEditRootFunc, LEditDelta и LEditIterations соответственно.
Свойствам Caption дайте значения Корень, Функция в корне, Неопределенность, Число
итераций. Кроме того, свойству ReadOnly всех 4-ех компонент дайте значения true,
сделав тем самым компоненты «только для чтения». В эти редакционные
окошки программа будет выводить результаты счета.
После этих редакций GroupBoxOut примет вид
В заключение поместите на форму компоненту TStatusBar из раздела Win32. В
ней будет сообщаться о текущем состоянии программы. Дайте ей имя StatusBar, а
свойство SimplePanel установите в true. Это означает, что строка статуса будет
использоваться как окно с комментирующим текстом.
В результате всех действий у класса формы TNonLinearEqTest появилось довольно
много полей. Все поля имеют доступ published (по умолчанию) и
перечисляются в описании класса на странице кода. Кроме того, имена полей
появились в стуктуре класса TNonLinearEqTest, изображенной в окне Structure.
Вопросы для самоконтроля
1. Как создать шаблон оконного приложения в Delphi?
2. Смысл директивы компилятору {$R *.res}.
3. Что находится в файле ресурсов оконного приложения в Delphi?
4. Как изменить иконку приложения в Delphi?
5. Что делает метод Initialize класса TApplication?
6. Как определяется адрес подпрограммы в Delphi?
7. Опишите параметры метода CreateForm класса TApplication.
8. Какова роль метода Run?
9. Как переключать между режимами code и design в среде Delphi?
10.Какую роль играет директива {$R *.dfm} в модуле формы?
11.Как подсоединить модуль с классами к оконному приложению в Delphi?
12.Как изменить имя формы?
13.Как увидеть содержание файла формы .dfm?
14.Какой смысл имеет свойство Caption формы?
15.Что такое клиентская область окна?
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
58
16.Какие изменения происходят в описании класса формы при размещении
на ней новой компоненты?
17.Приведите примеры группирующих компонент. Какова их роль?
18.Какова роль компонент типа TLabel?
19.Какое свойство наполняет список компоненты типа TComboBox?
20.Опишите свойства Hint, ShowHint и Tag компонент.
21.Каково назначение компонент типа TEdit и TLabeledEdit?
22.Какова роль компонент типа TButton, и какие типы кнопок есть в Delphi?
23.Каков смысл свойства ReadOnly компонент типа TEdit?
24.Зачем используются объекты типа TStatusBar в оконных приложениях?
Программный код формы в Delphi
В продолжение предыдущего модуля создается код оконного приложения,
тестирующего классы решения нелинейного уравнения в Delphi.
Секция интерфейса
Начните с описания постоянных и типов, которые будут использоваться в коде.
Для этого
 в интерфейсе модуля формы, сразу после списка uses и перед описанием
класса формы, наберите следующий текст
type
// Перечислимый тип состояния программы
// Обратите внимание на синтаксис описания перечислимого типа в Delphi
TState=(waiting, computing);
 В разделе private описания класса формы TNonLinearEqTest наберите
описание нескольких полей, которые будут использоваться в коде
// Хранит текущее число итераций
Iterations: integer;
// Хранит текущее состояние программы
FState: TState;
// Хранят текущие значения границ интервала изоляции
// абсолютной и относительной погрешностей
CurLeftSide, CurRightSide, CurAbsAcc, CurRelAcc:double;
 В том же разделе наберите заголовки нескольких методов и одного
свойства
// Возвращает левую часть уравнения
function f(x:double):double;
// Возвращает производную левой части уравнения
function df(x:double):double;
// Обработчик перед началом итераций
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
59
// Содержит код, который должен выполняться перед начало цикла итераций
procedure OnBeforeIterations(sender:TObject);
// Обработчик для каждой итерации.
// Содержит код, который должен выполняться на каждом шаге итераций
function OnStep(sender:TObject):Boolean;
// Обработчик после окончания итераций
procedure OnAfterIterations(sender:TObject);
// Устанавливает состояние программы (поле FState)
procedure SetState(value:TState);
// Восстанавливает редакционные окна вывода результатов
procedure RestoreOut(sender:TObject);
// Очищает редакционные окна вывода результатов
procedure ClearOut;
// Оценивает вводимые данные через редакционные окошки
procedure Validate(sender:TLabeledEdit);
// Возвращает и устанавливает состояние программы
property State:TState read FState write SetState;
Обратите внимание на синтаксис описания свойства (property) в Delphi.
Модификатор read используется для указания на то, откуда свойство получает
(читает) свое значение. Модификатор write – куда и как значение
записывается. В данном случае свойство State читает значение из поля FState, а
пишет с помощью метода SetState.
После этих редакций содержание интерфейсной секции модуля формы
NonLinearEqTest примет вид.
Секция реализации. Код методов
В секции реализации дается содержание методов класса формы TNonLinearEqTest,
перечисленных в интерфейсе. В заголовке каждого метода его собственному
имени должно предшествовать имя класса.
 В начале добавьте список uses Math; после слова imlementation. Модуль
Math содержит ряд функций, которые понадобятся в коде.
 Далее наберите текст с описанием постоянных данных, которые
потребуются в коде секции реализации
const
// массив левой границы интервалов изоляции по умолчанию
DefLeftSide:array [0..1] of double=(-1.5,0.1);
// массив правой границы интервалов изоляции по умолчанию
DefRightSide:array [0..1] of double=(2.0,0.9);
// массив типов классов решения нелинейного уравнения
NonLinearEqClass:array [0..5] of TNonLinearEqClass=
(TNonLinearEqBisection, TNonLinearEqSecant, TNonLinearEqFalsePos,
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
60
TNonLinearEqRidders, TNonLinearEqBrent, TNonLinearEqNewton);
// Сообщение об ошибке
MsgCaption='Error';
 Вспомните содержание первых двух методов f и df, которые возвращают
левую часть нелинейного уравнения и производную этой левой части.
Оно такое же, как у аналогичных методов, набранных в консольном
приложении.
function TNonLinearEqTest.f(x:double):double;
begin
Result :=NaN;
case ComboBoxFunction.ItemIndex of
0: Result :=sin(x);
1:
begin
if (x>1.0) or (x=0.0) then
// Порождается объект исключительной ситуации, если аргумент
// выходит из области определения функции
raise EArgumentOutOfRangeException.Create
('Значение аргумента выходит из области определения!');
result:=tan(x)-sqrt(1.0-x*x)/x;
end;
end;
end {f};
function TNonLinearEqTest.df(x:double):double;
begin
Result :=NaN;
case ComboBoxFunction.ItemIndex of
0: Result :=cos(x);
1:
begin
if (x>=1.0) or (x=0.0) then
raise EArgumentOutOfRangeException.Create
('Значение аргумента выходит из области определения!');
result:=1.0/Sqr(cos(x))+1.0/sqrt(1.0-Sqr(x))/Sqr(x)
end
end;
end {df};
Единственное отличие - в параметре выбора ComboBoxFunction.ItemIndex
оператора case. Здесь этот параметр является свойством ItemIndex
компоненты ComboBoxFunction. Выбор определенной функции в списке
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
61
компоненты ComboBoxFunction фиксирует значение свойства ItemIndex,
нумерующего элементы списка от 0.
 Следующие три метода вызываются объектом решения нелинейного
уравнения в разные моменты его жизни – перед итерационным циклом, на
каждом его шаге и после его окончания
procedure TNonLinearEqTest.OnBeforeIterations(sender:TObject);
begin
// Инициализирует поле числа итераций
Iterations:=0;
// Устанавливает значения в окошки вывода результата
RestoreOut(sender);
// Выполняет все поступившие сообщения
Application.ProcessMessages;
// Останавливает процесс на 1 сек (1000 мс)
Sleep(1000);
end {OnBeforeIteration};
function TNonLinearEqTest.OnStep(sender:TObject):Boolean;
begin
// Увеличивает поле числа итераций на единицу
Inc(Iterations);
// Устанавливает новые значения в окошки вывода результата
RestoreOut(sender);
// Выполняет все поступившие сообщения
Application.ProcessMessages;
// Останавливает процесс на 1 сек (1000 мс)
Sleep(1000);
// Возвращает true (прерывание цикла итераций)
// или false (продолжение цикла) в зависимости от
// флага Application.Terminated и состояния ожидания программы
Result:=Application.Terminated or (State=waiting)
end {OnStep};
procedure TNonLinearEqTest.OnAfterIterations(sender:TObject);
begin
RestoreOut(sender);
end {OnAfterIterations};
 Ознакомьтесь с методом SetState, который устанавливает значение поля
состояния FState.
procedure TNonLinearEqTest.SetState(value:TState);
begin
// В состоянии ожидания компоненты, помещенные
// на GroupBoxIn активизируются (свойство Enabled).
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
62
// В состоянии счета - дезактивируются.
// В зависимости от состояния устанавливается текст в строке статуса
if value=waiting then
begin
GroupBoxIn.Enabled:=true;
StatusBar.SimpleText:='waiting';
end else
begin
GroupBoxIn.Enabled:=false;
StatusBar.SimpleText:='computing';
// Очищаются окошки вывода результата
ClearOut;
end;
// Полю FState присваивается значение аргумента метода Set
FState:=value;
end {SetState};
Обратите внимание, что у метода SetState есть только один параметр value,
через который полю FState передается новое значение. Это именно то
значение, которое присваивается свойству State.
 Познакомьтесь с содержанием методов RestoreOut и ClearOut. Они
восстанавливают (restore) и очищают (clear) редакционные окошки вывода
результатов решения уравнения.
procedure TNonLinearEqTest.RestoreOut(sender:TObject);
begin
// Оператор as позволяет рассматривать ссылку sender
// как ссылку на объект типа TNonLinearEquation
// Ссылка sender является параметром метода
// Устанавливается в свойство Text в окошках вывода результата
with sender as TNonLinearEquation do
begin
LEditRoot.Text:=FloatToStr(Root);
LEditRootFunc.Text:=FloatToStr(f(Root));
LEditDelta.Text:=FloatToStr(Delta);
LEditIterations.Text:=IntToStr(Iterations);
end;
end {RestoreOut};
procedure TNonLinearEqTest.ClearOut;
begin
// Метод Clear очищает свойство Text в редакционных окошках вывода
LEditRoot.Clear; LEditRootFunc.Clear;
LEditDelta.Clear; LEditIterations.Clear;
end {ClearOut};
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
63
 Метод Validate предназначен для оценки информации, вводимой через
редакционные окошки ввода. Вот его содержание
procedure TNonLinearEqTest.Validate(sender:TLabeledEdit);
var CurValue:double;
begin
// Делается попытка ввести текст с редакционного окошка
// и преобразовать этот текст в вещественное число
// Число сохраняется в локальной переменной CurValue
try
CurValue:=StrToFloat(sender.Text);
except
// При неверно набранном формате числа вызывается метод
// MessageBox объекта Application с сообщением
// об ошибке
Application.MessageBox('Не верно набрано число! '+
'(возможно поставлена точка вместо запятой?!)',
MsgCaption, MB_ICONEXCLAMATION);
// В зависимости от окошка (свойство Tag параметра sender)
// в окошко возвращается прежнее значение
case Sender.Tag of
1:LEditLeft.Text:=FloatToStr(CurLeftSide);
2:LEditRight.Text:=FloatToStr(CurRightSide);
3:LEditAbsAcc.Text:=FormatFloat('#.##e-##',CurAbsAcc);
4:LEditRelAcc.Text:=FormatFloat('#.##e-##',CurRelAcc);
end;
// Метод завершается
Exit;
end;
// В зависимости от окошка проверяются условия попадания
// введенного числа в требуемый интервал значений
// Если условие не выполняется, об этом сообщается в окошке
// MessageBox и восстанавливается прежнее значение
// При нормальном вводе значение CurValue передается в соответствующее
// поле формы
case Sender.Tag of
1:
if CurValue>=CurRightSide then
begin
Application.MessageBox ('Левая граница интервала изоляции'+
' должна быть меньше правой!',
MsgCaption, MB_ICONEXCLAMATION);
LEditLeft.Text:=FloatToStr(CurLeftSide);
end else
CurLeftSide:=CurValue;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
64
2:
if CurValue<=CurLeftSide then
begin
Application.MessageBox ('Правая граница интервала изоляции'+
' должна быть больше левой!',
MsgCaption, MB_ICONEXCLAMATION);
LEditRight.Text:=FloatToStr(CurRightSide);
end else
CurRightSide:=CurValue;
3:
if CurValue<MinAbsAcc then
begin
Application.MessageBox (PAnsiChar(Format('Абсолютная погрешность '+
'должна быть больше или равна %g!', [MinAbsAcc])),
MsgCaption, MB_ICONEXCLAMATION);
LEditAbsAcc.Text:=FloatToStr(CurAbsAcc);
end else CurAbsAcc:=CurValue;
4:
if (CurValue<MinRelAcc) or (CurValue>MaxRelAcc) then
begin
Application.MessageBox(PAnsiChar(Format('Относительная погрешность '+
'должна быть в интервале [%g;%g]!',[MinRelAcc,MaxRelAcc])),
MsgCaption, MB_ICONEXCLAMATION);
LEditRelAcc.Text:=FloatToStr(CurRelAcc);
end else CurRelAcc:=CurValue;
end;
end {Validate};
 На этом исчерпывается код методов из раздела private класса
TNonLinearEqTest. Обратите внимание на элементы языка, встретившиеся в
коде.
o Оператор точка применяется при поименовании методов класса
при их описании, а также при вызове методов, полей и свойств
объектов;
o Выражение PAnsiChar(Format('Относительная погрешность '+, 'должна быть
в интервале [%g;%g]!', [MinRelAcc, MaxRelAcc])), встретившееся при
подстановке первого параметра в метод Application.MessageBox. Здесь
присутствует преобразование типов вида PAnsiChar(переменная).
Такое преобразование возможно, если типы совместимы. В данном
случае стандартная функция Format возвращает значение типа
string. 1-ый параметр метода MessageBox должен быть типа
PAnsiChar (тип указателя на символьный массив), поэтому требуется
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
65
преобразование типа строки к типу PAnsiChar. И это совместимые
типы.
У самой функции Format есть два параметра, первый из которых
является строкой, а второй имеет более сложный тип массива
специфических структур. Функция преобразует перечисленные
элементы массива (в данном случае действительные постоянные в
квадратных скобках MinRelAcc, MaxRelAcc) в строчные выражения.
Преобразование происходит по формату, указанному символами %g
(general – общий формат) в строке. Преобразованные значения
подставляются именно на те места, где находятся символы %g.
Таким образом, строчное выражение выглядит слитно, как единое
предложение, хотя и содержит в себе, вообще говоря, переменные
значения MinRelAcc и MaxRelAcc. Стоит обратить внимание на такой
«динамический» метод построения строк.
o Третьим параметром метода MessageBox является переменная целого
типа, указывающая на то, какой значок изображается в окне
сообщения. В данном случае используется целая константа
MB_ICONEXCLAMATION, соответствующая восклицательному знаку.
Код обработчиков событий в окне
Теперь следует создать код обработчиков событий, при наступлении которых
все эти методы будут работать.
 Создайте код обработчика события OnSelect компоненты ComboBoxFunction.
Для этого
o Выделите компоненту ComboBoxFunction на странице Design
o В инспекторе объектов перейдите на закладку Events (события) и
найдите в ней событие OnSelect
o Дважды щелкните по полю справа. В поле должна появиться строка
ComboBoxFunctionSelect – название обработчика по умолчанию.
Среда открывает окно кода и в нем появляется скелет метода
ComboBoxFunctionSelect. Наберите в нем следующий код
procedure TNonLinearEqTest.ComboBoxFunctionSelect(Sender: TObject);
begin
// Текущим значениям границ интервала изоляции
// присваиваются значения по умолчанию
CurLeftSide:=DefLeftSide[ComboBoxFunction.ItemIndex];
CurRightSide:=DefRightSide[ComboBoxFunction.ItemIndex];
// В редакционные окошки помещаются эти значения
// Стандартная функция FloatToStr преобразует число с плавающей запятой в строку
LEditLeft.Text:=FloatToStr (CurLeftSide);
LEditRight.Text:=FloatToStr (CurRightSide);
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
66
end;
Теперь каждый раз при выборе новой функции нелинейного уравнения из
компоненты ComboBoxFunction будут обновляться значения интервала
изоляции и содержание редакционных окошек с границами этого
интервала.
 Вслед за созданием окна, если свойство Visible равно true, события наступают
в следующей последовательности OnCreate, OnShow, OnActivate, OnPaint. Если
необходимо реализовать некий код, который должен работать сразу после
создания окна, то следует создать обработчик OnCreate. При этом надо
учитывать, что код обработчика события OnCreate будет выполняться только
один раз в жизненном цикле окна. Код обработчика OnActivate будет
выполняться, когда окно становится активным. Активным является окно,
имеющее фокус ввода, т.е. через него можно вводить информацию в
приложение. Если в приложении несколько окон, то в каждый момент
активно только одно из них. Фон заголовка активного окна отличается от
фона заголовка неактивного окна. В приложении с одним окном событие
OnActivate наступает фактически только один раз вслед за созданием окна.
Событие OnShow наступает, когда свойство окна Visible становится равным
true и окно начинает прорисовываться на экране. Событие OnPaint наступает
каждый раз при перерисовке окна. У формы есть обработчики событий
OnDestroy, наступающего перед освобождением окна, OnDeactivate – перед
потерей фокуса ввода, OnHide – перед тем, как окно станет невидимым.
Следует отметить, что при переходе фокуса ввода к окну другого
приложения, событие OnDeactivate окна не наступает. В этом случае следует
обрабатывать событие OnDeactivate объекта Application.
Создайте обработчик события OnCreate формы, срабатывающий сразу после
ее создания. Для этого
o в окне Design щелкните по поверхности формы, и в инспекторе
объектов перейдите на страничку Events (события).
Найдите в левой колонке событие OnCreate и дважды щелкните по
правой, пока пустой, клеточке. В клеточке должна появиться строка
FormCreate. Среда должна открыть окно кода и сформировать скелет
обработчика FormCreate.
o Наберите внутри обработчика (между begin и end) код с
комментариями так, что весь метод примет вид
procedure TNonLinearEqTest.FormCreate(Sender: TObject);
begin
// Устанавливаются строки для подсказок (hint)
// появляющихся вблизи окошек с погрешностями
LEditAbsAcc.Hint:='Абсолютная погрешность >= '+FormatFloat('#.##e-##',MinAbsAcc);
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
67
LEditRelAcc.Hint:='Относительная погрешность должна быть в интервале ['+
FormatFloat('#.##e-##',MinRelAcc)+';'+FormatFloat('#.##e-##',MaxRelAcc)+']';
// Устанавливаются первые элементы списков методов и функций
ComboBoxMethod.ItemIndex:=0;
ComboBoxFunction.ItemIndex:=0;
// Вызывается обработчик для установки интервала изоляции
ComboBoxFunctionSelect(self);
// Устанавливается состояние ожидания программы
State:=waiting;
// Устанавливаются значения погрешностей
LEditAbsAcc.Text:=FloatToStr(DefAbsAcc);LEditRelAcc.Text:=FloatToStr(DefRelAcc);
CurAbsAcc:=DefAbsAcc;CurRelAcc:=DefRelAcc;
end;
В этом фрагменте кода встречается еще одна форматирующая процедура
FormatFloat. Обратите внимание на ее два параметра и их смысл.
 Теперь наберите обработчик клика кнопки ButtonCompute. Для этого можно
просто дважды щелкнуть по самой кнопке с надписью Считай в форме.
Это способ задания так называемого «обработчика по умолчанию».
Появится скелет обработчика. Поместите в него код, приводящий к
вычислению корня.
procedure TNonLinearEqTest.ButtonComputeClick(Sender: TObject);
var nle:TNonLinearEquation;
begin
// Создание экземпляра класса, как
// элемента массива классов NonLinearEqClass,
// номер которого выбран из списка компоненты ComboBoxMethod
nle :=NonLinearEqClass[ComboBoxMethod.ItemIndex].Create;
// Установка свойств объекта nle
with nle do
begin
// Имена свойств OnBeforeIterations и OnAfterIterations здесь совпадают
// с именами методов-обработчиков, описаных в форме.
// Поэтому у методов формы имени ставятся расширения Self
OnBeforeIterations :=Self.OnBeforeIterations;
OnIteration :=OnStep;
OnAfterIterations :=Self.OnAfterIterations;
LeftHandSide :=f;
AbsAcc :=CurAbsAcc;
RelAcc :=CurRelAcc;
// Оператор is проверяет тип класса, которому принадлежит объект nle
// оператор as проводит приведение типа объекта nle к указанному типу
if nle is TNonLinearEqNewton then
(nle as TNonLinearEqNewton).Derivative:=df;
// Свойство State устанавливается в computing
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
68
State :=computing;
// Делается попытка вызвать метод определения корня
try
GetRoot(CurLeftSide,CurRightSide);
// Является ли попытка удачной или неудачной
// (возникает исключительная ситуация),
// в любом случае выполняются операторы секции finally
finally
Free; State :=waiting;
end
end
end;
 Наберите обработчики, фиксирующие ввод данных через окошки,
определяющие интервал изоляции и погрешности. В этом случае надо
присоединить один и тот же обработчик к событиям, поступающим от
разных объектов – четырех редакционных окошек ввода LEditLeft,
LEditRight, LEditAbsAcc и LEditRelAcc типа TLabeledEdit. Начните с события
OnExit, наступающего в момент переноса фокуса с редакционного окошка
на какой-либо другой объект интерфейса. Для этого
o Войдите в окно инспектора объектов и в верхней строке откройте
список всех объектов формы. Выберите в нем первый объект
LEditLeft. Перейдите на закладку Events и найдите событие OnExit. В
поле справа наберите имя LEditInExit (это, конечно, произвольное
имя), которое будет использовать обработчик событий выхода из
редакционных окошек. Нажмите Enter. Появится, как обычно, окно
кода со скелетом обработчика.
o Вновь войдите в окно инспектора объектов и найдите в верхнем
списке объект LEditRight. Затем, на страничке Event найдите опять
событие OnExit. Теперь в правом поле откройте список уже
имеющихся обработчиков нужного типа. Выберите обработчик с
именем LeditInExit, который только что был создан.
o Повторите предыдущий пункт для объектов LEditAbsAcc и
LEditRelAcc.
Теперь у всех четырех окошек ввода будет один и тот же обработчик
выхода. В теле обработчика поместите строку кода, в которой вызывается
метод, проверяющий правильность вводимой информации.
procedure TNonLinearEqTest.LEditInExit (Sender: TObject);
begin
// При вызове метода Validate, описанного выше, производится преобразование типа
// переменой Sender.
// Подумайте, почему?
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
69
Validate(Sender as TLabeledEdit);
end;
 Пользователь может зафиксировать ввод нажатием клавиши Enter.
Поэтому следует предусмотреть соответствующий обработчик. Создайте
один общий обработчик события OnKeyPress с именем LeditInKeyPress для
тех же самых объектов – редакционных окошек ввода LEditLeft, LEditRight,
LEditAbsAcc и LEditRelAcc. В нем поместите строку вызова метода Validate
при условии нажатия на клавишу Enter.
procedure TNonLinearEqTest.LeditInKeyPress
(Sender: TObject; var Key: Char);
begin
// Символ клавиши Enter имеет код 13.
// Преобразование кода в соответствующий ему символ
// в Delphi проводится служебным символом # (sharp).
if Key=#13 then Validate(Sender as TLabeledEdit);
end;
 Наконец, создайте обработчик события входа OnEnter в какой-либо объект,
принадлежащий групповой компоненте GroupBoxIn. Для этого достаточно
написать обработчик входа в сам объект GroupBoxIn. В этом обработчике
следует очищать содержимое окошек вывода, в которых могли остаться
результаты предыдущего счета. Опять войдите в окно инспектора
объектов. Выберите в верхнем списке объект GroupBoxIn. В окне Events
найдите событие OnEnter и дважды щелкните по правой клеточке.
Наберите в появившемся скелете обработчика следующий код
procedure TNonLinearEqTest.GroupBoxInEnter(Sender: TObject);
begin
// Вызывает очистку окошек вывода
ClearOut;
end;
Теперь при любой попытке ввести информацию через любую из
компонент, принадлежащей GroupBoxIn, окошки в GroupBoxOut очистятся от
предыдущего результата.
На этом, собственно, написание кода модуля формы завершается. Можно еще
раз проверить секцию реализации этого модуля, посмотрев его полный код.
Проведите компиляцию проекта командой Build из меню Project. Все должно
пройти нормально. Запустите приложение на счет командой Run из меню
Project (зеленая стрелка). Проверьте работу программы в счете (runtime).
Попытайтесь изменить интервал изоляции, сделав его не верным, или вводите
произвольные символы в окошки ввода, чтобы проверить реакцию кода.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
70
Опробуйте разные методы решения одного и того же уравнения. Посмотрите
число итераций. В общем, займитесь отладкой программы.
Вопросы для самоконтроля
1. Синтаксис описания перечислимого типа в Delphi.
2. Смысл модификаторов read и write в описании свойства в Delphi.
3. Как работает оператор множественного выбора в методах f и df?
4. Как работает метод Application.ProcessMessages?
5. Как работает процедура Sleep?
6. Какой смысл у свойства enabled контролов?
7. Какой смысл параметра value у методов Set?
8. Какой смысл свойства State в данной форме?
9. Какой смысл параметра sender в методе RestoreOut?
10.Что делает метод Clear у контролов?
11.Объясните логику метода Validate.
12.Что делает метод Application.MessageBox, и каков смысл его параметров?
13.Когда применяется оператор точка в Delphi?
14.Что делает стандартная функция Format, и каков смысл ее параметров?
15.Преобразование и совместимость типов в Delphi.
16.Создание обработчиков событий формы и ее компонент в Delphi.
17.Что делает стандартная функция FloatToStr в Delphi?
18.Объясните логику кода обработчика ComboBoxFunctionSelect.
19.Какова последовательность событий при создании окна, и какой смысл
этих событий?
20.Смысл процедуры FormatFloat и ее параметров.
21.Логика содержания обработчика FormatFloat.
22.Смысл использования Self в обработчике ButtonComputeClick.
23.Смысл оператора try… finally.
24.Опишите технологию присоединения одного обработчика, событиям,
наступающим в разных компонентах.
25.Оператор # в Delphi.
26.Логика кода обработчика GroupBoxInEnter.
Программирование в C++ Borland
В этом модуле строится версия классов решения нелинейного уравнения на
языке C ++ фирмы Borland .
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
71
Введение
Структура программного модуля в C++ несколько отличается от структуры
модуля, написанного на Delphi. В определенной степени можно сказать, что
интерфейсу дельфийского модуля соответствует отдельный физический файл
программного модуля на C++, именуемый «хэдер», или файл заголовков. Файл
заголовков имеет расширение .h. Другой файл, имеющий расширение .cpp и то
же имя, содержит обычно то, что находится в разделе реализации модуля
Delphi. Оба файла .h и .cpp образуют неразрывную пару, являющуюся единым
программным модулем.
Файл заголовков
Модуль имеет имя uNonLinearEquation. Весь код хэдера заключен «в скобки»
защитного блокиратора вида
#ifndef uNonLinearEquationH
#define uNonLinearEquationH
<Здесь находится код хэдера>
#endif
Дело в том, что хэдер в процессе компиляции становится частью общего модуля
с тем же именем, объединяясь со второй частью модуля – файлом с
расширением .cpp. Файл .cpp для этого содержит специальную директиву
компилятора вида #include «имя хэдера» (см. ниже). Блокиратор предохраняет
такое соединение двух файлов от дублирования информации, содержащейся в
хэдере. Для этого в блокираторе с помощью директивы компилятора #define
определяется некоторый символ, представляющий имя файла с добавленной в
конце буквой H (такие добавления, делающиеся в конце слова, называют суффиксами, в
отличие от префиксов, добавляемых в начале слова).
В нашем случае это символ uNonLinearEquationH. При первом обращении к хэдеру
этот символ не определен и условная директива компилятора #ifndef … («если не
определен символ …») позволяет выполняться всем операторам вплоть до
завершающей директивы #endif. Так как среди этих операторов имеется
директива #define, определяющая этот символ, то при любой попытке повторно
использовать код хэдера условие #ifndef не выполнится и код не будет подшит.
Изучая код хэдера, обратите внимание на следующие отличия в синтаксисе
описания на языках Delphi и C++:
1. В отличие от Delphi, описание каждого типа отдельно должно
предваряться служебным словом typedef.
2. В описании на языке C в начале указывается тип, а затем имя
переменной или типа. Например, double const MinRelAcc=1e-16.
3. Тип метода класса формируется с помощью двух модификаторов доступа
__fastcall и __closure. Модификатор __fastcall указывает на то, что
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
72
4.
5.
6.
7.
вызов метода должен быть оптимизирован транслятором с целью
наибыстрейшего доступа к его параметрам. Модификатор __closure
специфичен именно для C++ Builder. Он указывает на то, что следующий
далее указатель на функцию (к примеру, *TLeftHandSide в нашем модуле)
должен рассматриваться как указатель на метод класса. То, что речь идет
об указателе, отмечается стандартным для C символом звездочки *
(астериск).
При описании класса предок указывается через двоеточие, следующего за
именем наследника, как в выражении class TNonLinearEquation: public
TObject. Указание на предка в описании класса может содержать
модификатор доступа типа public как в данном случае. Это означает, что
модификаторы доступа наследуемых членов класса либо остаются у
наследника такими же, как и у предка (в случае public), либо их уровень
доступа снижается (модификаторы protected и private). В случае
protected уровень public у предка снижается до protected у
наследника, а в случае private уровни public и protected у предка
снижаются до private у наследника. Если модификатор не указан,
транслятор подставляет private.
Модификаторы доступа к членам класса private, protected, public
внутри описания класса должны завершаться двоеточием.
В языке C нет процедур. Если функция не возвращает переменную
какого-либо типа, то возвращаемый тип именуется void как в описании
void __fastcall SetAbsAcc (double const value);.
Даже если у функции отсутствуют параметры, скобки все равно пишутся
как, например, в double __fastcall BasicLoop();.
Модификатор virtual указывается перед именем метода как в описании
8.
bool __fastcall virtual DoIteration (double& CurRoot, double& CurDelta) = 0;
В этой же строке инициализатор метода =0 означает, что метод является
абстрактным и не содержит тела.
9. В отличие от Delphi, где параметры по ссылке обозначаются словом var, в
языке C ссылка на параметр обозначается &, как в описанном выше
заголовке.
10.При повторном описании виртуального метода в классе-наследнике
модификатор доступа override не используется.
11.Обратите внимание на отличия в описании свойств.
12.Конструктор объектов в C++ - это метод с тем же именем, что и класс.
Конструктор не возвращает значение какого-либо типа, даже void.
13.При написании идентификаторов в языке C следует учитывать их
зависимость от того, используется прописная или строчная буквы. Так
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
73
идентификаторы root и Root различаются. Это используется при описании
полей и соответствующих свойств.
14.При описании классов исключительных ситуаций описание наследников
потребовало описания конструкторов, содержащих параметр. Это общее
правило языка C++: если класс содержит описание конструктора с
параметрами, то его наследники должны вновь описывать эти
конструкторы, даже если они не добавляют своего кода.
// Директивы компилятору в C начинаются с символа # (sharp)
// Директива ifndef проверяет определен ли символ, стоящий за ней.
// В данном случае символом является uNonLinearEquationH.
// Если символ не определен,
// то весь код до появления директивы #endif не компилируется.
// В данном модуле директива #endif находится в самом конце
#ifndef uNonLinearEquationH
// Директива #define определяет символ, следующий за ней.
// В данном случае символом является uNonLinearEquationH.
// Это определение сохраняется до появления директивы #undef для того же символа.
#define uNonLinearEquationH
// Директива компилятору, требующая включения в модуль файла заголовков
// библиотечного модуля math
#include "math.h"
// Тип метода левой части уравнения f(x)=0
typedef double __fastcall(__closure *TLeftHandSide)(double);
// Тип метода - обработчика события при каждой итерации сужения
typedef bool __fastcall(__closure *TOnIteration)(TObject*);
// Обратите внимание на синткасис описания постоянных в языке C
double const
MinRelAcc=1e-16,
// минимальная допустимая относительная погрешность
MaxRelAcc=0.01,
// максимальная допустимая относительная погрешность
DefRelAcc=MinRelAcc, // относительная погрешность по умолчанию
MinAbsAcc=1e-15,
// минимальная допустимая абсолютная погрешность
DefAbsAcc=MinAbsAcc; // абсолютная погрешность по умолчанию
// Абстрактный класс решения нелинейного уравнения
// Класс наследует от TObject библиотеки VCL
// Модификатор public перед предком означает, что доступ ко всем наследуемым
// членам класса у наследника такой же, как у предка
class TNonLinearEquation: public TObject
{
//Поля и методы класса, доступные только членам данного класса
private:
TLeftHandSide leftHandSide; //хранит указатель на левую часть уравнения
double root;
//хранит текущее значение корня
// Хранят значения аргумента и функции на левой и правой границах интервала изоляции
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
74
double leftSideArg, rightSideArg;
double leftSideFunc, rightSideFunc;
// Условием завершения решения является приближение к корню до
// некоторой минимальной по абсолютной величине неопределенности
// (абсолютная погрешность), либо неопределенности малой по отношению
// к значению самого корня (относительная погрешность)
double delta;
// хранит текущую неопределенность в значении корня (>0)
double relAcc, absAcc;// хранят текущие погрешности
// Поля onBeforeIterations,onAfterIterations хранят указатели на обработчики событий.
// Событие Before Iteration наступает перед началом итерационного цикла.
// Событие After Iteration наступает сразу после завершения итерационного цикла.
// Тип обработчика TNotifyEvent является стандартным.
// Он описан в модуле Classes библиотеки среды.
// Обратите внимание на оператор ::, разделяющий в C
// локальное имя объекта (в данном случае TNotifyEvent)
// и имя так называемого «пространства имен» (в данном случае Classes).
// Пространство имен (namespace) в C соответствует имени модуля в Delphi.
Classes::TNotifyEvent onBeforeIterations, onAfterIterations;
// Событие On Iteration наступает в конце каждой итерации,
// если метод DoIteration не прерывает итерационный цикл, возвратив true.
// Поле onIteration хранит указатель на обработчик события OnIteration типа TOnIteration.
// Тип TOnIteration описан выше в этом же модуле и является
// «типом указателя на метод класса».
TOnIteration onIteration;
// Совершает основной итерационный цикл
// Метод не имеет параметров. Но скобки () в языке C пишутся всегда.
double __fastcall BasicLoop ();
// Методы типа Set используются свойствами класса, описанными ниже.
// Устанавливает текущее значение абсолютной погрешности
// Модификатор void означает, что функция не возвращает значение.
// Это подобно процедуре в Delphi.
void __fastcall SetAbsAcc (double const value);
// Устанавливает текущее значение относительной погрешности
void __fastcall SetRelAcc (double const value);
//Методы, доступные наследникам
protected:
// Метод Initialize предназначен для возможной инициализации полей,
// используемых наследниками. Хотя он не абстрактный, но пустой.
// Вызывается внутри BasicLoop до входа в основной цикл и
// непосредственно перед наступлением события BeforeIterations.
void __fastcall virtual Initialize ();
// Абстрактный метод выполнения одной итерации приближения к корню DoIteration.
// Метод DoIteration реализует конкретный алгоритм поиска корня
// на одном шаге итерации.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
75
// В настоящем классе является абстрактным и должен быть перекрыт наследником.
// Абстрактность метода в C++ указывается оператором присвоения = 0.
// Своими двумя параметрами по ссылке метод DoIteration должен вернуть
// текущие значения корня CurRoot и его неопределенность CurDelta (>0).
// Обратите внимание на синтаксис описания параметров по ссылке в C++ (знак &).
// Эти параметры используются в условии завершения итерационного цикла.
// Кроме того, метод DoIteration возвращает свое
// условие завершения итерационного цикла значениями true или false.
bool __fastcall virtual DoIteration (double& CurRoot, double& CurDelta) = 0;
// Свойства и методы, доступные любому приложению
public:
// Свойства
// Обратите внимание на синтаксис описания свойств в языке C++ Borland
// возвращает и устанавливает текущее значение абсолютной погрешности
__property double AbsAcc= {read = absAcc, write = SetAbsAcc};
// возвращает текущую неопределенность корня
__property double Delta= {read = delta};
// возвращает левую часть уравнения
__property TLeftHandSide LeftHandSide = {read = leftHandSide, write = leftHandSide};
// возвращает текущее значение аргумента в левой точке
__property double LeftSideArg = {read = leftSideArg};
// возвращает текущее значение функции в левой точке
__property double LeftSideFunc = {read = leftSideFunc};
// Свойства, устанавливающие поля обработчиков событий класса
__property Classes::TNotifyEvent OnAfterIterations = {write = onAfterIterations};
__property Classes::TNotifyEvent OnBeforeIterations = {write = onBeforeIterations};
__property TOnIteration OnIteration = {write = onIteration};
// возвращает и устанавливает текущее значение относительной погрешности
__property double RelAcc = {read = relAcc, write = SetRelAcc};
// возвращает текущее значение аргумента в правой точке
__property double RightSideArg = {read = rightSideArg};
// возвращает текущее значение функции в правой точке
__property double RightSideFunc = {read = rightSideFunc};
// возвращает текущее значение корня
__property double Root = {read = root};
// Конструктор класса. Обратите внимание на синтаксис описания конструктора в C.
// Обнуляет все поля.
// Поля абсолютной и относительной погрешностей устанавливаются в значения
// по умолчанию.
__fastcall TNonLinearEquation (): TObject ()
{
AbsAcc=DefAbsAcc; RelAcc=DefRelAcc;
}
// Метод GetRoot определяет и возвращает корень уравнения root
// на интервале изоляции [aLeftSideArg; aRightSideArg]
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
76
// Обратите внимание на синтаксис описания виртуальных методов в C
double __fastcall virtual
GetRoot (double const aLeftSideArg, double const aRightSideArg);
// Завершение описания класса TNonLinearEquation
};
// Типы классов исключений, используемых в методах класса TNonLinearEquation
// и его наследников.
// Обратите внимание на необходимость описания конструктора наследника,
// если конструктор предка не является конструктором по умолчанию.
// При этом вызывается конструктор предка.
// Выражение <: Exception (Msg)> определяет вызов конструктора предка.
// Объект класса создается, если не задан аргумент или какое-либо поле
class EArgumentNullException: public Exception
{
public:
__fastcall EArgumentNullException (const AnsiString Msg): Exception (Msg) {}
};
// Объект класса создается, если аргумент или параметр
// выходит за границы требуемого интервала
class EArgumentOutOfRangeException: public Exception
{
public:
__fastcall EArgumentOutOfRangeException (const AnsiString Msg): Exception (Msg) {}
};
// Объект класса создается, если аргумент не удовлетворяет требуемым условиям
class EArgumentException: public Exception
{
public:
__fastcall EArgumentException (const AnsiString Msg): Exception (Msg) {}
};
// Класс решает уравнение f(x) = 0 методом деления отрезка пополам (Bisection)
class TNonLinearEqBisection: public TNonLinearEquation
{
private:
// Поля, хранящие текущие значения аргумента, функции и неопределенности корня
double curArg,curFunc,curDelta;
protected:
// Метод, реализующий алгоритм поиска корня методом деления пополам
bool __fastcall DoIteration (double& CurRoot, double& CurDelta);
// Метод, инициализирующий поля класса перед входом в итерационный цикл
void __fastcall Initialize();
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
77
};
// Класс решает уравнение f(x)=0 методом секущих
class TNonLinearEqSecant: public TNonLinearEquation
{
private:
// Поля, хранящие текущие значения аргумента, функции и неопределенности корня
double curArg,curFunc,curDelta,
// Поля, хранящие текущие значения аргумента и функции последнего приближения
lastArg,lastFunc;
protected:
bool __fastcall DoIteration (double& CurRoot, double& CurDelta);
void __fastcall Initialize();
};
// Класс решает уравнение f(x)=0 методом ложного положения
class TNonLinearEqFalsePos: public TNonLinearEquation
{
private:
// Хранят текущую неопределенность корня и текущие значения аргумента и функции
double curDelta,curArg,curFunc,
// Поля, хранящие текущие значения аргумента и функции на границах
// с отрицательным и положительным значениями функции
negSideArg, posSideArg, negSideFunc, posSideFunc,
deltaArg; // Поле текущей разности значений аргумента на краях интервала
protected:
bool __fastcall DoIteration (double& CurRoot, double& CurDelta);
void __fastcall Initialize();
};
// Класс решает уравнение f(x)=0 методом Ридерса
class TNonLinearEqRidders: public TNonLinearEquation
{
private:
double leftArg, rightArg, leftFunc, rightFunc, curArg, curFunc;
protected:
bool __fastcall DoIteration (double& CurRoot, double& CurDelta);
void __fastcall Initialize();
};
// Класс решает уравнение f(x)=0 методом Брента
class TNonLinearEqBrent: public TNonLinearEquation
{
private:
double firstPointArg, firstPointFunc, secondPointArg, secondPointFunc,
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
78
thirdPointArg, thirdPointFunc, firstDelta, secondDelta, tempDelta;
protected:
bool __fastcall DoIteration (double& CurRoot, double& CurDelta);
void __fastcall Initialize();
};
// Класс решает уравнение f(x)=0 методом Ньютона-Рафсона
class TNonLinearEqNewton: public TNonLinearEquation
{
private:
TLeftHandSide derivative;
double lowArg, highArg, deltaArg, prevDeltaArg, curArg, curFunc, curDerivative;
protected:
bool __fastcall DoIteration (double& CurRoot, double& CurDelta);
void __fastcall Initialize();
public:
__property TLeftHandSide Derivative = {read = derivative, write = derivative};
// Метод GetRoot определяет и возвращает корень уравнения root
// на интервале изоляции [aLeftSideArg; aRightSideArg]
// Обратите внимание на синтаксис описания виртуальных методов у наследников
// Ни модификатор virtual ни override не указывается в языке C++
double __fastcall GetRoot
(double const aLeftSideArg, double const aRightSideArg);
};
// Директива компилятору #endif завершает область кода,
// которая управляется директивой #ifdef или #ifndef
#endif
По этой ссылке находится текст файла заголовков.
Существенным отличием хэдера от интерфейсной секции дельфийского модуля
является то, что в хэдере возможна реализация методов класса, а в
интерфейсной секции Delphi – нет. Так в описании класса TNonLinearEquation
реализация конструктора размещена в хэдере.
CPP файл
При анализе кода, написанного на C++ в приведенном модуле, рекомендуется
обратить внимание на отличия в записи основных операторов и выражений в
сравнении с кодом в Delphi. Это очень поучительно.
// Директива компилятору #include требует включения в данном месте кода
// указанного за ней модуля.
// Если имя включаемого модуля окружено скобками вида <>,
// то его поиск осуществляется в директориях, установленных средой по умолчанию
#include <vcl.h>
// Директива #pragma hdrstop указывает окончание списка файлов заголовков,
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
79
// предназначенных для прекомпиляции.
// Прекомпиляция значительно увеличивает скорость компиляции.
#pragma hdrstop
// Если имя включаемого модуля окружено кавычками "",
// то его поиск начинается с текущего каталога и продолжается до поиска
// в директориях, установленных средой по умолчанию
#include "values.h"
#include "Math.hpp"
#include "uNonLinearEquation.h"
// Директива #pragma package(smart_init) обеспечивает оптимальную последовательность
// инициализации модулей, включаемых в данный модуль.
#pragma package(smart_init)
// Итерационный цикл
// Обратите внимание на синтаксис написания заголовка метода
// при его реализации в модуле cpp.
// Имя класса (в данном случае TNonLinearEquation) отделено от имени метода
// (в данном случае BasicLoop) оператором :: (а не оператором «точка», как в Delphi).
// Это тот же оператор, что отделяет имя пространства имен от имени объекта.
double __fastcall TNonLinearEquation::BasicLoop()
{
Initialize(); // Инициализация полей в наследниках
// Вызов обработчика перед началом цикла итераций
// Обратите внимание, что условие в операторе if
// пишется в языке C всегда в круглых скобках.
// В качестве условного выражения не обязательно должно быть выражение типа bool,
// как в Delphi.
// В данном случае условным выражением является значение «указателя на метод».
// Если значение указателя отличается от нуля (указатель «в никуда»),
// то метод существует в памяти и условное выражение возвратит true.
// Служебное слово this в C аналогично self в Delphi.
// Оно содержит ссылку на объект, вызывающий метод
if (onBeforeIterations) onBeforeIterations(this);
// Основной цикл
do {}
while (!(DoIteration( root, delta)// Вызов алгоритма итерации
|| // оператор логического сложения (или)
// Текущий корень покинул интервал изоляции?
root > rightSideArg || root < leftSideArg ||
// вызов обработчика onIteration
onIteration && onIteration(this)
// Проверка условия прекращения цикла по допустимой погрешности
|| delta<2.0*relAcc*fabs(root)+0.5*absAcc));
// Вызов обработчика после конца цикла итераций
if (onAfterIterations) onAfterIterations(this);
if (root > rightSideArg || root < leftSideArg)
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
80
// Создание объекта исключительной ситуации
// Для создания ИС язык C использует служебное слово throw, а не raise, как в Delphi
throw EArgumentOutOfRangeException("Алгоритм покинул интервал изоляции!");
// Оператор return в языке C вызывает выход из функции и передает возвращаемое
// значение, если оно есть. В данном случае это значение root.
return root;
}
// Установка абсолютной погрешности
void __fastcall TNonLinearEquation::SetAbsAcc(double const value)
{
// Условное выражение возвращает значение, стоящее после знака ?,
// если условие выполняется, и после двоеточия :, если оно не выполняется
absAcc= value<MinAbsAcc?MinAbsAcc:value;
}
// Установка относительной погрешности
void __fastcall TNonLinearEquation::SetRelAcc(double const value)
{
// Здесь «двойное» условное выражение
relAcc= value<MinRelAcc?MinRelAcc:value>MaxRelAcc?MaxRelAcc:value;
}
// Реализация методов с доступом protected
// Метод инициализации в этом классе пуст
void __fastcall TNonLinearEquation::Initialize()
{
}
// Метод поиска решения уравнения на интервале изоляции [aLeftSideArg;aRightSideArg]
double __fastcall TNonLinearEquation::GetRoot
(double const aLeftSideArg, double const aRightSideArg)
{
if (!leftHandSide)
throw EArgumentNullException("Уравнение не задано!");
if (aLeftSideArg >= aRightSideArg)
throw EArgumentOutOfRangeException("Левая граница больше правой!");
leftSideArg = aLeftSideArg; rightSideArg = aRightSideArg;
//Значения на границе
leftSideFunc = leftHandSide(leftSideArg);
if (0 == leftSideFunc) return root = leftSideArg;
rightSideFunc = leftHandSide(rightSideArg);
if (0 == rightSideFunc) return root = rightSideArg;
if (leftSideFunc * rightSideFunc > 0.0)
throw EArgumentException("Это не интервал изоляции!");
root = 0.5 * (rightSideArg + leftSideArg); delta = rightSideArg - leftSideArg;
//Основной цикл
return root = BasicLoop();
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
81
}
// Реализация методов классов-наследников
// Методы класса TNonLinearEqBisection (метод деления пополам)
// Реализация алгоритма деления интервала пополам
bool __fastcall TNonLinearEqBisection::DoIteration
(double& CurRoot, double& CurDelta)
{
// Описание локальных переменных в C может даваться в любом месте тела функции
double tempArg;
// Обратите внимание на действия операторов присвоения в языке C
// Операторы присвоения здесь не только посылают значение нужному операнду,
// но и возвращают это значение, в отличие от Delphi.
curFunc = LeftHandSide(CurRoot = (tempArg = curArg + (curDelta*= 0.5)));
// Обратите внимание на стандартную функцию определения модуля abs.
// Для переменных типа чисел с плавающей запятой она имеет префикс f (от float).
CurDelta = fabs(curDelta);
if (curFunc <= 0.0) curArg = tempArg;
return 0.0 == curFunc;
}
void __fastcall TNonLinearEqBisection::Initialize()
{
if (LeftSideFunc > 0.0)
{
curDelta = LeftSideArg - RightSideArg; curArg = RightSideArg; curFunc = RightSideFunc;
}
else
{
curDelta = RightSideArg - LeftSideArg; curArg = LeftSideArg; curFunc = LeftSideFunc;
}
}
// Реализация методов класса TnonLinearEqSecant (Метод секущей)
// Реализация алгоритма секущей
bool __fastcall TNonLinearEqSecant::DoIteration
(double& CurRoot, double& CurDelta)
{
curDelta = (lastArg - curArg) * curFunc/(curFunc - lastFunc);
lastArg = curArg; lastFunc = curFunc;
curFunc = LeftHandSide(CurRoot = curArg+= curDelta);
CurDelta = fabs(curDelta);
return 0.0 == curFunc;
}
void __fastcall TNonLinearEqSecant::Initialize()
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
82
{
// Обратите внимание на список операторов присваивания,
// выполняющийся внутри условного выражения.
// Такая форма существует только в языке C, но ее нет ни в Delphi, ни в C#
// Список возвращает последний элемент: RightSideFunc или LefttSideFunc
lastFunc = fabs(LeftSideFunc) < fabs(RightSideFunc)?
(curArg = LeftSideArg, curFunc = LeftSideFunc, lastArg = RightSideArg, RightSideFunc):
(curArg = RightSideArg, curFunc = RightSideFunc, lastArg = LeftSideArg, LeftSideFunc);
}
// Реализация методов класса TnonLinearEqFalsePos (метод ложного положения)
// Реализация алгоритма ложного положения
bool __fastcall TNonLinearEqFalsePos::DoIteration
(double& CurRoot, double& CurDelta)
{
curFunc = LeftHandSide(curArg = negSideArg +
deltaArg * negSideFunc/(negSideFunc - posSideFunc));
if (curFunc < 0.0)
{
curDelta = negSideArg - curArg; negSideArg = curArg; negSideFunc = curFunc;
}
else
{
curDelta = posSideArg - curArg; posSideArg = curArg; posSideFunc = curFunc;
}
deltaArg = posSideArg - negSideArg;
CurRoot = curArg; CurDelta = fabs(curDelta);
return 0.0 == curFunc;
}
void __fastcall TNonLinearEqFalsePos::Initialize()
{
posSideFunc = LeftSideFunc<0.0?
(negSideArg = LeftSideArg, negSideFunc = LeftSideFunc,
posSideArg = RightSideArg, RightSideFunc):
(negSideArg = RightSideArg, negSideFunc = RightSideFunc,
posSideArg = LeftSideArg, LeftSideFunc);
deltaArg = posSideArg - negSideArg;
}
// Реализация методов класса TnonLinearEqRidders (метод Ридерса)
// Реализация алгоритма Ридерса
bool __fastcall TNonLinearEqRidders::DoIteration
(double& CurRoot, double& CurDelta)
{
double midpointArgValue = 0.5*(leftArg + rightArg);
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
83
double midpointFuncValue = LeftHandSide(midpointArgValue);
double temp = sqrt(midpointFuncValue * midpointFuncValue – leftFunc * rightFunc);
if (0.0 == temp) return true;
double newArg = midpointArgValue + (midpointArgValue - leftArg) *
(leftFunc >= rightFunc?1.0:-1.0) * midpointFuncValue/temp;
if ((CurDelta = fabs(curArg-newArg)) < AbsAcc)
{
CurRoot = curArg; return true;
}
curFunc = LeftHandSide(curArg = newArg);
if (midpointFuncValue < 0.0 ^ curFunc < 0.0)
{
leftArg = midpointArgValue; leftFunc = midpointFuncValue;
rightArg = curArg; rightFunc = curFunc;
}
else
if (leftFunc < 0.0 ^ curFunc < 0.0)
{
rightArg = curArg; rightFunc = curFunc;
}
else
if (rightFunc < 0.0 ^ curFunc < 0.0)
{
leftArg = curArg; leftFunc = curFunc;
}
CurRoot = curArg; CurDelta = fabs(rightArg - leftArg);
return 0.0 == curFunc;
}
void __fastcall TNonLinearEqRidders::Initialize()
{
leftArg = LeftSideArg; rightArg = RightSideArg;
leftFunc = LeftSideFunc; rightFunc = RightSideFunc;
// Постоянная MAXDOUBLE является стандартной
curArg = MAXDOUBLE;
}
// Реализация методов класса TnonLinearEqBrent (метод Брента)
// Реализация алгоритма Брента
bool __fastcall TNonLinearEqBrent::DoIteration
(double& CurRoot, double& CurDelta)
{
double numerator, denominator, aFraction, bFraction, aMin, bMin, CurAcc;
if (secondPointFunc * thirdPointFunc > 0.0)
{
thirdPointArg = firstPointArg; thirdPointFunc = firstPointFunc;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
84
firstDelta = secondPointArg - firstPointArg; secondDelta = firstDelta;
}
if (fabs(thirdPointFunc) < fabs(secondPointFunc))
{
firstPointArg = secondPointArg; secondPointArg = thirdPointArg;
thirdPointArg = firstPointArg;
firstPointFunc = secondPointFunc; secondPointFunc = thirdPointFunc;
thirdPointFunc = firstPointFunc;
}
tempDelta = 0.5 * (thirdPointArg - secondPointArg);
CurAcc = 2.0 * RelAcc * fabs(secondPointArg) + 0.5 * AbsAcc;
if ((CurDelta = fabs(tempDelta)) < CurAcc || 0.0 == secondPointFunc)
{
CurRoot = secondPointArg; return true;
}
if (fabs(secondDelta) >= CurAcc && fabs(firstPointFunc) > fabs(secondPointFunc))
{
bFraction = secondPointFunc/firstPointFunc;
if (firstPointArg == thirdPointArg)
{
numerator = 2.0 * tempDelta * bFraction; denominator = 1.0 - bFraction;
} else
{
denominator = firstPointFunc/thirdPointFunc; aFraction = secondPointFunc/thirdPointFunc;
numerator = bFraction * (2.0 * tempDelta * denominator * (denominator - aFraction)
- (secondPointArg - firstPointArg) * (aFraction - 1.0));
denominator = (denominator - 1.0) * (aFraction - 1.0) * (bFraction - 1.0);
}
if (numerator > 0.0) denominator = -denominator;
numerator = fabs(numerator);
aMin = 3.0 * tempDelta * denominator - fabs(CurAcc * denominator);
bMin = fabs(secondDelta * denominator);
if (2.0 * numerator < aMin < bMin?aMin:bMin)
{
secondDelta = firstDelta; firstDelta = numerator/denominator;
} else
{
firstDelta = tempDelta; secondDelta = firstDelta;
}
} else
{
firstDelta = tempDelta; secondDelta = firstDelta;
}
firstPointArg = secondPointArg; firstPointFunc = secondPointFunc;
if (fabs(firstDelta) > CurAcc) secondPointArg+= firstDelta;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
85
else secondPointArg+= Sign(tempDelta) * CurAcc;
secondPointFunc = LeftHandSide(secondPointArg);
CurRoot = secondPointArg;
return 0.0 == secondPointFunc;
}
void __fastcall TNonLinearEqBrent::Initialize()
{
tempDelta = (thirdPointArg = secondPointArg = RightSideArg) - (firstPointArg = LeftSideArg);
firstPointFunc = LeftSideFunc; thirdPointFunc = secondPointFunc = RightSideFunc;
}
// Реализация методов класса TnonLinearEqNewton (метод Ньютона-Рафсона)
// Реализация алгоритма Ньютона-Рафсона
bool __fastcall TNonLinearEqNewton::DoIteration
(double& CurRoot, double& CurDelta)
{
double tempArg;
if (((curArg-highArg)*curDerivative-curFunc)*((curArg-lowArg)*curDerivative-curFunc) > 0
|| fabs(2.0 * curFunc) > fabs(prevDeltaArg * curDerivative))
{
prevDeltaArg = deltaArg;
deltaArg = 0.5 * (highArg - lowArg);
curArg = lowArg + deltaArg;
if (lowArg == curArg)
{
CurRoot = curArg; CurDelta = fabs(deltaArg); return true;
}
}
else
{
prevDeltaArg = deltaArg;
deltaArg = curFunc/curDerivative;
tempArg = curArg; curArg-= deltaArg;
if (curArg == tempArg)
{
CurRoot = curArg; CurDelta = fabs(deltaArg); return true;
}
}
CurDelta = fabs(deltaArg); CurRoot = curArg;
if (CurDelta < 2.0 * RelAcc * fabs(curArg)+0.5 * AbsAcc) return true;
curFunc = LeftHandSide(curArg); curDerivative = derivative(curArg);
if (curFunc < 0.0) lowArg = curArg; else highArg = curArg;
return 0.0 == curFunc;
}
void __fastcall TNonLinearEqNewton::Initialize()
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
86
{
highArg = LeftSideFunc<0.0?(lowArg = LeftSideArg,RightSideArg):
(lowArg = RightSideArg,LeftSideArg);
curArg = Root; deltaArg = prevDeltaArg = Delta; curFunc = LeftHandSide(curArg);
curDerivative = derivative(curArg);
}
//Метод GetRoot для метода Ньютона-Рафсона проверяет наличие производной функции f(x)
double __fastcall TNonLinearEqNewton::GetRoot(
double const aLeftSideArg, double const aRightSideArg)
{
if (!derivative) throw EArgumentNullException("Производная не задана!");
return TNonLinearEquation::GetRoot (aLeftSideArg, aRightSideArg);
}
По этой ссылке дается текст модуля .cpp.
Консольное приложение на C++ Borland
По аналогии с Delphi здесь строится консольное приложение, тестирующее
классы решения нелинейного уравнения в среде C++ Builder 6.
Внешне среда C++ Builder мало отличается от Turbo Delphi. Так же, как и в
предыдущем случае
 В стандартном каталоге Projects, созданном C Builder при установке
продукта, создайте рабочий каталог с именем nonLinEqTestProjects.
 Командой New -> Other… главного меню File создайте Project Group.
Назовите ее nonLinEqTestGroup и сохраните в только что созданном каталоге
nonLinEqTestProjects.
 Добавьте в группу шаблон консольного приложения. Среда создаст шаблон
файла модуля с расширением .cpp и ряд других файлов, используемых в
процессе работы над проектом.
 Назовите проект prcnslCBNonLinEq, а модуль cnslCBNonLinEq, сохранив их в том
же каталоге командами Save As… и Save Projects As… из главного меню
File.
Появится редакционное окно с шаблоном файла модуля вида
//--------------------------------------------------------------------------#include <vcl.h>
#pragma hdrstop
//--------------------------------------------------------------------------#pragma argsused
int main(int argc, char* argv[])
{
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
87
return 0;
}
//--------------------------------------------------------------------------Здесь
 В образованном шаблоне есть функция main, в теле которой находится
только один оператор возврата return. У функции два параметра. Параметр с
именем argc имеет целый тип, а параметр с именем argv является массивом
символов, т.е. строкой. В описании параметра argv стоит символ * (астериск),
который означает в языке C указатель на массив символов. Функция main
возвращает значение целого типа.
Функция main указанного типа (или, как говорят, сигнатуры) играет особую
роль в программе, написанной на языке C. С функции main начинается и в
ней оканчивается работа программы. В качестве параметров функция main
использует параметры программы, или параметры командной строки.
Параметры программы вводятся в программу при активизации ее exe-файла
командной строкой. Командная строка вводится с помощью команды Run из
меню кнопки Пуск. В командной строке набирается имя файла программы и
следующий за ней через пробел список параметров, разделенных
пробелами. В списке могут находиться любые сочетания символов. Эти
сочетания и есть значения параметров программы. Функция main получает в
качестве значения своего первого параметра argc число параметров
программы, указанных в командной строке, а в качестве второго параметра
argv – указатель на область памяти, где эти параметры хранятся. В качестве
целого числа функция main возвращает код статуса программы, который
может использовать другой процесс.
 Директива компилятору #pragma argsused в данном случае применяется к
функции main. Она подавляет предупреждение компилятора о том, что
функция main нигде в теле не использует заявленные параметры argc и argv.
Наша программа не будет иметь параметров командной строки.
Командой New -> Unit из главного меню File откройте новый модуль. Среда
даст ему имя unit1. Модуль состоит из двух файлов – заголовочного и файла
.cpp. Командой Save As… сохраните этот модуль под именем uNonLinearEquation.
Сотрите текст, набранный средой, и скопируйте на его место код,
рассмотренный выше. Естественно, сделайте это отдельно для заголовочного
файла и файла .cpp. Из меню Project командой Compile unit скомпилируйте
модуль, убедившись, что в нем нет синтаксических ошибок.
После того, как модуль uNonLinearEquation стал частью приложения (проверьте
это, открыв окно Project Manager из меню View), внесите в основной модуль
cnslCBNonLinEq приложения между строками #pragma hdrstop и #pragma argsused
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
88
текст, который повторяет на языке C++ описание класса консольного
приложения написанного выше на Delphi. Внимательно изучите комментарий.
// Здесь компилятор включит текст файла заголовков модуля uNonLinearEquation
#include "uNonLinearEquation.h"
// Здесь транслятор включит библиотечные модули, используемые в проекте.
// В модуле iostream описаны стандартные объекты ввода/вывода cin и cout.
// Использование этих объектов в какой-то мере аналогично Read и Write в Delphi.
#include <iostream>
// В модуле typeinfo описана стандартная функция typeid, которая возвращает тип
// объекта, заданного в качестве аргумента.
// Функция typeid используется в коде метода main
#include "typeinfo.h"
// В модуле Math описана постоянная NaN
#include "Math.hpp"
// В пространстве имен std, описанном в модуле iostream, находятся объекты ввода/вывода.
// Можно было бы не писать using namespace. В этом случае следовало бы использовать
// полные имена – вместо cout писать std::cout, а вместо cin – std::cin.
// Роль служебного слова using здесь подобна uses в Delphi.
using namespace std;
// Описание класса тестирования нелинейного уравнения
class NonLinEqTest
{
private:
// Хранят текущий номер уравнения и число итераций
int item, itr;
// Хранит указатель на текущий объект класса решения нелинейного уравнения.
// То, что это именно указатель, определено оператором «звездочка» * (астериск).
TNonLinearEquation* nleq;
// Возвращает левую часть нелинейного уравнения
double __fastcall f(double x);
// Возвращает производную левой части нелинейного уравнения
double __fastcall df(double x);
// Обработчик перед началом итераций
void __fastcall beforeIterations(TObject* sender);
// Обработчик на каждом шаге итерационного цикла
bool __fastcall onIteration(TObject* sender);
// Обработчик после окончания итераций
void __fastcall afterIterations(TObject* sender);
public:
// Конструктор. Инициализирует последовательность случайных чисел
__fastcall NonLinEqTest()
{
Randomize();
};
// Тестирует классы решения нелинейного уравнения
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
89
bool __fastcall JustDoIt();
};
void __fastcall NonLinEqTest::afterIterations(TObject* sender)
{
// Выводит в черное окно число итераций
cout << "\nIterations " << itr << "\n";
}
void __fastcall NonLinEqTest::beforeIterations(TObject* sender)
{
// Обнуляет счетчик итераций
itr=0;
}
bool __fastcall NonLinEqTest::onIteration(TObject* sender)
{
// Увеличивает значение счетчика итераций на единицу
itr++;
// Возвращает false, чтобы не прерывать итерационного цикла
return false;
}
double __fastcall NonLinEqTest::f(double x)
{
// По значению item выбирает левую часть уравнения
switch (item)
{
case 0:return sin(x);
case 1:
if (x>1.0 || x==0.0)
throw EArgumentOutOfRangeException
("Аргумент вышел из области определения!");
return tan(x)-sqrt(1.0-x*x)/x;
default:return NaN;
}
}
double __fastcall NonLinEqTest::df(double x)
{
// По значению item выбирает производную левой части уравнения
switch (item)
{
case 0:return cos(x);
case 1:
if (x>=1.0 || x==0.0)
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
90
throw EArgumentOutOfRangeException ("Аргумент вышел из области определения!");
return 1.0/cos(x)/cos(x)+1.0/sqrt(1.0-x*x)/x/x;
default:return NaN;
}
}
bool __fastcall NonLinEqTest::JustDoIt()
{
// Уничтожает объект в памяти, если он уже был создан
if (nleq) delete nleq;
// задает случайное значение номеру уравнения
item=random(2);
// По случайному значению определяет класс решения нелинейного уравнения
// Обратите внимание на синтаксис создания экземпляра класса в C.
// Сравните этот код с дельфийским кодом. Подумайте о различиях.
switch (random(6))
{
case 0:
nleq= new TNonLinearEqBisection; break;
case 1:
nleq= new TNonLinearEqSecant;
break;
case 2:
nleq= new TNonLinearEqFalsePos; break;
case 3:
nleq= new TNonLinearEqRidders;
break;
case 4:
nleq= new TNonLinearEqBrent;
break;
case 5:
nleq= new TNonLinearEqNewton;
break;
}
// Устанавливает свойства объекта решения нелинейного уравнения
// Обратите внимание на синтаксис вызова свойств и методов объекта в C++.
// Вместо оператора точки здесь используется оператор вида ->,
// соединяющий имя объекта с его свойством или методом.
// Такой оператор применяется в C для указателей на объект, которым и является
// в данном случае nleq (посмотрите на его описание выше).
// Если бы nleq был определен просто как объект класса TNonLinearEquation, а не
// указатель, то оператором была бы точка, а не ->.
// Ниже в коде встречается подобный случай.
nleq->OnBeforeIterations=beforeIterations;
nleq->OnIteration=onIteration;
nleq->OnAfterIterations=afterIterations;
nleq->LeftHandSide=f;
// Если тип объекта (его класс) есть класс TNonLinearEqNewton,
// то устанавливается свойство Derivative
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
91
// Обратите внимание на определение типа объекта.
// Это равносильно использованию оператора is в Delphi и C#
if (typeid(*nleq)==typeid(TNonLinearEqNewton))
// Обратите внимание на приведение типа в C++.
// Операция равносильна использованию оператора as в Delphi и C#
((TNonLinearEqNewton*)nleq)->Derivative=df;
// На экран выводится имя класса (используется метода ClassName, как и вDelphi)
// Обратите внимание на преобразование типов строки.
// Метод ClassName класса TObject (предка всех классов) возвращает строку
// типа ShortString.
// С помощью метода c_str() класса AnsiString строка преобразуется в указатель
// на массив символов, который используется cout.
// Здесь экземпляр класса AnsiString создается одним из его конструкторов
// (с тем же именем, что и класс) и сразу вызывается метод c_str().
// Обратите внимание, что метод c_str() вызывается оператором точка, а не ->,
// т.к. конструктор класса AnsiString возвращает сам объект, а не указатель на него.
cout << "\n\nMethod " << AnsiString(nleq->ClassName()).c_str() << "\n";
// На экран выводится левая часть уравнения
cout << "Equation: " << (item==0?"sin(x) = 0":"tn(x)-sqrt(1.0-x*x)/x = 0");
// Вводятся значения левой и правой границы интервала изоляции
double ls,rs;
cout << "\nEnter left side:";
cin >> ls;
cout << "\nEnter right side:";
cin >> rs;
// Делается попытка найти корень
try
{
cout << "\nroot = "<<nleq->GetRoot(ls,rs);
}
// При возникновении исключительной ситуации объект ИС становится
// параметром E catch (аналог except в Delphi)
catch (Exception &E)
{
// На экран выводится сообщение об исключительной ситуации
ShowMessage(E.Message);
}
// С консоли вводится какой-либо символ
char c;
cout << "\nEnter a char (x - to end):";
cin >> c;
// Функция возвращает true или false в зависимости от того,
// не совпадает введенный символ с x, или совпадает
return c!='x';
}
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
92
Теперь внутрь метода main внесите следующий текст.
// Создается экземпляр класса NonLinEqTest, описанного выше
NonLinEqTest* nleTest= new NonLinEqTest();
// Организуется цикл, условием завершения которого является значение false,
// возвращаемое методом JustDoIt объекта nleTest
// Обратите внимание, что даже если цикл пустой,
// скобки {} между do и while ставить необходимо.
do {}
while (nleTest->JustDoIt());
// Память под объект nleTest освобождается.
// delete в C++ играет роль метода Free в Delphi
delete nleTest;
Ссылка на текст основного модуля приложения cnslCBNonLinEq.
Вопросы для самоконтроля
Смысл защитного блокиратора в модуле файла заголовков.
Синтаксис директив компилятору в C++.
Синтаксис описание типов, постоянных и переменных в C++.
Синтаксис описания типа метода класса в C++ Builder.
Синтаксис заголовка описания класса в C++. Смысл модификатора
доступа при указании предка.
6. Синтаксис модификаторов доступа к членам класса в C++.
7. Описание подпрограмм в языке C.
8. Синтаксис описания виртуальных и абстрактных методов в C++ Builder.
9. Синтаксис описания параметров по ссылке в C++ Builder.
10.Синтаксис описания свойств в C++ Builder.
11.Синтаксис описания конструкторов объектов в C++.
12.Синтаксис идентификаторов в C.
13.Описание конструкторов у наследников в С++.
14.Смысл оператора :: в языке С.
15.Смысл директивы #include в языке C.
16.Синтаксис оператора if в языке C.
17.Тип выражения в условии оператора if в C.
18.Смысл служебного слова this в C++.
19.Служебное слово throw в C++.
20.Порядок описания локальных переменных в языке C.
21.Синтаксис списка оператора присваивания в языке C.
22.Смысл стандартной постоянной MAXDOUBLE.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
1.
2.
3.
4.
5.
93
23.Смысл функции main и ее параметров в консольном приложении.
24.Какие объекты используются для операций ввода/вывода в консольном
приложении?
25.Что делает стандартная функция typeid?
26.Синтаксис использования пространства имен std.
27.Какой смысл имеет операция * (астериск) в C?
28.Какой оператор уничтожает объект в памяти в языке C++?
29.Какой смысл имеет оператор ->?
30.Синтаксис приведения типа в C.
31. Смысл метода c_str.
32. Смысл оператора точка в С++.
33. Вызов метода без описания объекта через вызов конструктора в C++.
34.Синтаксис оператора try…catch в C.
Оконное приложение на C++ Borland
В этом модуле строится оконное приложение в среде C++ Borland, тестирующее
классы решения нелинейного уравнения.
Для создания оконного приложения
 Откройте группу проектов nonLinEqTestGroup, созданную ранее
 Добавьте с помощью контекстного меню в окне Project Manager к этой
группе новый проект типа Application. Среда должна создать и открыть
файлы формы с именем unit1 и создать файл проекта с именем Project1.
 Командой Save Project As… из главного меню File сохраните в каталоге
nonLinEqTestProjects файлы формы под именем fNonLinEqTest и файл проекта
под именем prNonLinEqTest.
 Откройте файл проекта командой View Source… из главного меню Project.
Вот текст, который среда должна создать в файле проекта
//--------------------------------------------------------------------------#include <vcl.h>
#pragma hdrstop
//--------------------------------------------------------------------------USEFORM("fNonLinEqTest.cpp", Form1);
//--------------------------------------------------------------------------WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
94
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
catch (...)
{
try
{
throw Exception("");
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
}
return 0;
}
//------------------------------------------------------------------------
Этот файл редко приходится редактировать. Его состояние поддерживается
средой. С одной стороны он похож на аналогичный файл проекта оконного
приложения в Delphi:
 здесь есть ссылка на файл формы, представленная в виде USEFORM
("fNonLinEqTest.cpp", Form1);.
 есть так же вызов тех же трех методов, что и в проекте Delphi
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
Отличия
 в синтаксисе первого параметра метода CreateForm. Служебное слово
__classid возвращает в C++ указатель на VMT класса, который является
аргументом (в данном случае TForm1). В Delphi сам идентификатор класса
возвращает эту ссылку.
 в том, что вызов методов происходит внутри стандартной для оконных
приложений на C функции WinMain. Эта функция аналогична функции main
консольного приложения, рассмотренного выше. Только применяется она в
оконных приложениях, поэтому имеет дополнительные параметры: два
параметра типа HINSTANCE определяют указатели на главные окна
приложения в среде Windows (первый указывает на текущее приложение,
второй - на предыдущее и равен нулю) и параметр типа int, который
определяет, как окно будет изображено. В параметре типа LPSTR находится
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
95
ссылка на командную строку, как и в обычном методе main. Все эти
параметры используются редко.
 еще в том, что вызов трех названных выше методов помещен в блок try. В
Delphi обработка исключительных ситуаций, возникающих в программе и не
обработанных программистом, происходит автоматически по умолчанию. В
языке C это требует явной записи. При возникновении внутри любого из
трех методов не обработанной исключительной ситуации, сводящейся к
появлению объекта класса Exception (или его наследника) управление
передается в первый блок catch с аргументом Exception &exception. Внутри
блока catch (Exception &exception) метод Application-> ShowException создает окно
с сообщением об исключительной ситуации. Если возникшая ИС не
порождает объект класса Exception, то управление передается в блок
catch(…). В этом блоке искусственно создается объект ИС класса Exception с
помощью оператора throw, и созданный объект ИС передается еще одному
блоку catch (Exception &exception), где и выводится на экран тем же методом
Application->ShowException, как неопознанный.
Среда готовит также шаблон файлов модуля формы. Здесь два файла – файл
заголовков с расширением .h и файл с кодом (расширение .cpp). Шаблон файла
заголовков формы имеет вид
//--------------------------------------------------------------------------#ifndef fNonLinEqTestH
#define fNonLinEqTestH
//--------------------------------------------------------------------------#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//--------------------------------------------------------------------------class TForm1 : public TForm
{
__published:
// IDE-managed Components
private: // User declarations
public:
// User declarations
__fastcall TForm1(TComponent* Owner);
};
//--------------------------------------------------------------------------extern PACKAGE TForm1 *Form1;
//--------------------------------------------------------------------------#endif
Кроме уже известных нам директив компилятору #ifndef fNonLineqTestH, #define
fNonLinEqTestH и #endif в файле заголовков формы среда помещает
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
96
 директивы включения стандартных модулей библиотеки VCL
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
для доступа к классам этой библиотеки.
 Скелет описания пользовательского класса формы TForm1 – наследника
библиотечного класса TForm. Модификатор перед TForm указывает на то, что
все члены класса TForm наследуются классом TForm1 без изменения их
уровней доступа. В скелете даются так же модификаторы разделов по
уровням доступа с комментарием и, что отличает класс формы в C++ от
Delphi, заголовок конструктора формы TForm1. Параметром конструктора
Owner является указатель (обозначается *) на библиотечный класс TComponent.
Это стандартная сигнатура конструкторов библиотеки VCL.
 Декларация
extern PACKAGE TForm1 *Form1;
объявляет, что переменная Form1, являющаяся указателем на объект класса
TForm1, доступна всем внешним модулям.
Конкретное значение объявленный указатель Form1 получает после
выполнения метода Application->CreateForm, как и Delphi, в файле проекта,
рассмотренном выше.
Дизайн окна
На практике содержание файла проекта в оконном приложении является
стандартным и не столь существенно, т.к. оно обычно не требует ручной
редакции. Редактировать следует модуль формы, как и в Delphi.
С этой целью, как и в случае оконного проекта в Delphi перейдите к
строительной площадке окна формы. На странице Properties инспектора
объектов найдите свойство Name и замените стоящую там строку Form1 на
NonLinearEqTest. Так будет называться наша форма. Нажмите Enter. Обратите
внимание, что среда автоматически изменила имя класса на TNonLinearEqTest.
Изменения произошли не только в модулях формы fNonLinEqTest.cpp и
fNonLinEqTest.h, но и в главном файле проекта prNonLinEqTest.cpp (откройте этот
файл из меню Project командой View Source). Изменилось так же и содержание
файла формы fNonLinEqTest.dfm.
В среде C++ Builder все компоненты находятся в главном окне в области
палитры компонент. Большая часть из тех, что будут использоваться,
расположены в разделе Standart.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
97
 В разделе Standart найдите компоненту класса TGroupBox и щелкните на
ней, а затем на клиентской области формы. На площадке появится
изображение контрола с именем GroupBox1. В инспекторе объектов
измените его имя (свойство Name) на GroupBoxIn, а значение свойства
Caption на In. Немного увеличьте его размеры. Обратите внимание, что в
файле заголовков формы произошли некоторые изменения. А именно, в
списке членов класса TNonLinearEqTest непосредственно за заголовком
класса появилось описание поля TGroupBox *GroupBoxIn;. Поле GroupBoxIn
хранит ссылку на объект класса TGroupBox, который был помещен на
форму. Перед описанием нового поля находится модификатор доступа
__published. Сохраните редакцию командой Save из меню File.
 Вернувшись на строительную площадку, перенесите на форму еще один
объект класса TGroupBox из палитры компонент. Назовите новый
контрол GroupBoxOut, а его свойству Caption дайте значение Out.
Группирующая компонента GroupBoxOut будет площадкой для контролов,
управляющих выводом результатов на экран. Расширьте границы
GroupBoxOut, расположив его достаточно симметрично в окне по
отношению к GroupBoxIn.
 Наполните GroupBoxIn компонентами из палитры компонент, которые
понадобятся для ввода информации в приложение:
 «Метка», или компонента типа TLabel. Поместите ее в верхнюю левую
часть GroupBoxIn. Назовите LabelMethod, а Caption дайте значение Method.
 Вторая метка типа TLabel. Поместите ее в верхнюю правую часть
GroupBoxIn. Назовите LabelFunc, а свойству Caption дайте значение Функция.
 Под левой меткой Method поместите компоненту типа TComboBox из
палитры. Назовите ComboBoxMethod. В нем будет располагаться список
методов решения нелинейного уравнения. Найдите свойство Items и
щелкните справа по кнопке с тремя точками. Должно открыться окно
String List Editor, в поле которого наберите список методов решения
уравнения следующего содержания
Bisection
Secant
False Position
Ridders'
Brent's
Newton's
 Этот список должен появляться при щелчке по правой кнопке контрола
ComboBoxMethod. Свойству ItemIndex дайте значение 0 вместо прежнего
значения -1. В изображении компонента должно появиться слово Bisection
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
98
– первая строка списка. Свойство Style в инспекторе объектов измените на
csDropDownList.
 Еще один объект класса TComboBox поместите симметрично под меткой
Функция. Назовите его ComboBoxFunc. В нем будет список функций
нелинейных уравнений. Внесите в свойство Items список
sinx
tg(x)-x/sqrt(1-x^2)
 Измените свойство ItemIndex на 0 и Style на csDropDownList.
 В палитре компонент, в разделе Additional выберите компоненту
TLabeledEdit. Перенесите ее на форму в GroupBoxIn и поместите ниже
контрола GroupBoxMethod. Назовите LEditLeft. В нем будет помещаться
число – левая граница интервала изоляции корня. Измените свойство
LabelPosition на lpLeft. Метка, поясняющая смысл редакционного окошка
должна переместиться влево. В инспекторе объектов выберите свойство
EditLabel и расширьте его, щелкнув по кнопке с плюсом слева. В
появившихся полях найдите свойство метки Caption и дайте ему значение
LeftSide=. Наконец, в инспекторе объектов найдите свойство Hint и
впишите в нем строку Левая граница должна быть меньше правой!. Свойство
ShowHint установите в true. Установите свойству Tag значение 1.
 Такую же компоненту типа TLabeledEdit поместите чуть правее, на одной
высоте с предыдущей. Назовите ее LEditRight. Через него будет вводиться
число – правая часть интервала изоляции корня. Аналогично
предыдущему контролу поместите метку слева, изменив свойство
LabelPosition на lpLeft. Свойству Caption метки дайте значение RightSide=. В
свойство Hint вбейте строку Правая граница должна быть больше левой!.
Свойство ShowHint установите в true. Установите свойству Tag значение 2.
 Поместите на GroupBoxIn новую компоненту типа TGroupBox в левой части,
ниже LEditLeft. В этой компоненте будут расположены редакционные
окошки, в которых будут задаваться погрешности решения нелинейного
уранения. Поэтому назовите ее GroupBoxAcc, а свойству Caption дайте
значение Погрешности.
 Внутрь GroupBoxAcc поместите два редакционных окошка типа TLabeledEdit.
Назовите их LEditAbsAcc и LEditRelAcc соответственно, а свойствам Caption
их меток дайте значения Абсолютная и Относительная. Через эти окошки
будут вводиться абсолютная и относительная погрешности. Установите
свойству Tag этих компонент значения 3 и 4 соответственно. Установите
свойство ShowHint у обеих кошек в значение true. Но свойство Hint оставьте
пустым. Его значение будет задаваться в программе динамически.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
99
 Внутрь GroupBoxIn, но вне GroupBoxAcc, справа от последнего поместите
кнопку (объект типа TButton). Назовите ее ButtonCompute, а свойству Caption
дайте значение Считай. Эта кнопка будет инициировать процесс счета.
Теперь заселите GroupBoxOut.
Поместите на него 4 компоненты типа TLabeledEdit в два столбца по паре и
назовите их LEditRoot, LEditRootFunc, LEditDelta и LEditIterations соответственно.
Свойствам Caption дайте значения Корень, Функция в корне, Неопределенность, Число
итераций. Кроме того, свойству ReadOnly всех 4-ех компонент дайте значения true,
сделав тем самым компоненты «только для чтения». В эти редакционные
окошки программа будет выводить результаты счета.
В заключение поместите на форму компоненту TStatusBar с палитры компонент.
В ней будет сообщаться о текущем состоянии программы. Дайте ей имя
StatusBar, а ее свойство SimplePanel в инспекторе объектов установите в true. Это
означает, что строка статуса будет использоваться как окно с комментирующим
текстом.
Программный код. Файл заголовков формы
 Откройте модуль классов uNonLinearEquation и присоедините его к проекту
через Project Manager командой Add.
 Затем в файл заголовков формы fNonLinEqTest.h непосредственно перед
описанием класса формы добавьте строку включения этого файла
#include "uNonLinearEquation.h"
Это позволит использовать классы решения нелинейного уравнения в
приложении. Добавьте также строку включения модуля Math
#include "Math.hpp"
 Добавьте ниже еще две строки кода,
double const DefLeftSide[2]={-0.5,0.1}, DefRightSide[2]={1.0,0.9};
typedef enum {waiting, computing} TState; // тип состояния
в которых описываются два массива с постоянными значениями по
умолчанию левой DefLeftSide и правой DefRightSide границ интервала
изоляции двух уравнений, и перечислимый тип TState с двумя элементами в
списке waiting и computing. Поле типа TState будет хранить состояние
программы.
 В раздел описания формы в том же модуле заголовков формы добавьте
следующие поля, свойство и методы. Смысл их тот же, что и у дельфийских
членов класса формы.
// Хранит текущее состояние программы
TState state;
// Хранит указатель на текущий объект класса нелинейного уравнения
TNonLinearEquation* nle;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
100
// Хранит текущее значение корня уравнения
double root;
// Хранит текущее число итераций
int iterations;
// Устанавливает состояние программы
void __fastcall SetState(TState value);
// Устанавливает и возвращает состояние программы
__property TState State = {read= state, write = SetState};
// Хранят текущие значения границ интервала изоляции корня,
// а также абсолютную и относительную погрешности.
double curLeftSide,curRightSide,curAbsAcc,curRelAcc;
// Возвращает левую часть уравнения
double __fastcall f(double x);
// Возвращает производную левой части уравнения
double __fastcall df(double x);
// Обработчики
// Перед началом итераций
void __fastcall beforeIterations(TObject* sender);
// Во время итераций (на каждом шаге)
bool __fastcall onIteration(TObject* sender);
// После окончания цикла итераций
void __fastcall afterIterations(TObject* sender);
// Оценивает информацию, вводимую через окошки ввода
void __fastcall Validate(TLabeledEdit* sender);
// Восстанавливает редакционные окна вывода результатов
void __fastcall RestoreOut(TObject* sender);
// Очищает редакционные окна вывода результатов
void __fastcall ClearOut();
На этом редактирования файла заголовка завершено.
Перейдите к файлу формы fNonLinEqTest.cpp.
Первоначально содержимое этого файла имеет вид
//--------------------------------------------------------------------------#include <vcl.h>
#pragma hdrstop
#include "fNonLinEqTest.h"
//--------------------------------------------------------------------------#pragma package(smart_init)
#pragma resource "*.dfm"
TNonLinearEqTest *NonLinearEqTest;
//--------------------------------------------------------------------------__fastcall TNonLinearEqTest::TNonLinearEqTest (TComponent* Owner)
: TForm(Owner)
{
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
101
}
Смысл директив компилятору был рассмотрен выше при создании модуля
классов решения нелинейного уравнения. Здесь среда добавила пустое тело
конструктора формы. Оставьте его пока пустым, но обратите внимание на
синтаксис вызова конструктора предка TForm(Owner) – через двоеточие после
имени конструктора. В языке C вызов конструктора предка необходим, даже
если новый конструктор не добавляет своего кода.
После строки #include "fNonLinEqTest.h" добавьте строку включения библиотечного
модуля typeinfo, который потребуется для работы
#include "typeinfo.h".
Методы формы
После этого добавьте в модуль формы код описанных в заголовке методов
класса TNonLinearEqTest.
Он аналогичен коду соответствующих методов в дельфийском проекте.
Сравните их. Проанализируйте языковые различия.
double __fastcall TNonLinearEqTest::f(double x)
{
switch (ComboBoxFunc->ItemIndex)
{
case 0:return sin(x);
case 1:
if (x>1.0 || x==0.0)
throw EArgumentOutOfRangeException
("Аргумент вышел из области определения!");
return tan(x)-sqrt(1.0-x*x)/x;
default:return NaN;
}
}
double __fastcall TNonLinearEqTest::df(double x)
{
switch (ComboBoxFunc->ItemIndex)
{
case 0:return cos(x);
case 1:
if (x>=1.0 || x==0.0)
throw EArgumentOutOfRangeException ("Аргумент вышел из области определения!");
return 1.0/cos(x)/cos(x)+1.0/sqrt(1.0-x*x)/x/x;
default:return NaN;
}
}
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
102
void __fastcall TNonLinearEqTest::beforeIterations(TObject* sender)
{
iterations=0;
RestoreOut(sender);
Application->ProcessMessages();
Sleep(1000);
}
bool __fastcall TNonLinearEqTest::onIteration(TObject* sender)
{
iterations++;
RestoreOut(sender);
Application->ProcessMessages();
Sleep(1000);
return Application->Terminated || State==waiting;
}
void __fastcall TNonLinearEqTest::afterIterations(TObject* sender)
{
RestoreOut(sender);
}
void __fastcall TNonLinearEqTest::SetState(TState value)
{
if (value==waiting)
{
GroupBoxIn->Enabled=true;
StatusBar->SimpleText="waiting";
}
else
{
GroupBoxIn->Enabled=false;
StatusBar->SimpleText="computing";
ClearOut();
}
state=value;
}
void __fastcall TNonLinearEqTest::Validate(TLabeledEdit* sender)
{
double curValue;
try
{
curValue=StrToFloat(sender->Text);
}
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
103
catch (Exception &e)
{
ShowMessage(e.Message+
". Не верно набрано число! (возможно, поставлена точка вместо запятой?!)");
//или
//Application->ShowException(&e);
switch (sender->Tag)
{
case 1:LEditLeft->Text=FloatToStr(curLeftSide);return;
case 2:LEditRight->Text=FloatToStr(curRightSide);return;
case 3:LEditAbsAcc->Text=FormatFloat("#.##e-##",curAbsAcc);return;
case 4:LEditRelAcc->Text=FormatFloat("#.##e-##",curRelAcc);return;
}
}
switch (sender->Tag)
{
case 1:
if (curValue>=curRightSide)
{
ShowMessage("Левая граница интервала изоляции должна быть меньше правой!");
LEditLeft->Text=FloatToStr(curLeftSide);
}
else
curLeftSide=curValue;
return;
case 2:
if (curValue<=curLeftSide)
{
ShowMessage("Правая граница интервала изоляции должна быть больше левой!");
LEditRight->Text=FloatToStr(curRightSide);
}
else
curRightSide=curValue;
return;
case 3:
if (curValue<MinAbsAcc)
{
ShowMessage(Format("Абсолютная погрешность должна быть больше или равна %g!",
ARRAYOFCONST((MinAbsAcc))));
LEditAbsAcc->Text=FloatToStr(curAbsAcc);
}
else curAbsAcc=curValue;
return;
case 4:
if (curValue<MinRelAcc || curValue>MaxRelAcc)
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
104
{
ShowMessage(Format("Относительная погрешность должна быть в интервале [%g;%g]!",
ARRAYOFCONST((MinRelAcc,MaxRelAcc))));
LEditRelAcc->Text=FloatToStr(curRelAcc);
}
else curRelAcc=curValue;
return;
}
}
void __fastcall TNonLinearEqTest::RestoreOut(TObject* sender)
{
LEditRoot->Text=FloatToStr(((TNonLinearEquation*)sender)->Root);
LEditRootFunc->Text=FloatToStr(f(((TNonLinearEquation*)sender)->Root));
LEditDelta->Text=FloatToStr(((TNonLinearEquation*)sender)->Delta);
LEditIterations->Text=IntToStr(iterations);
}
void __fastcall TNonLinearEqTest::ClearOut()
{
LEditRoot->Clear(); LEditRootFunc->Clear();
LEditDelta->Clear(); LEditIterations->Clear();
}
Обработчики формы
Введите обработчики событий, как в дельфийском проекте.
 Создайте код обработчика события OnSelect компоненты ComboBoxFunc.
Для этого
o Выделите компоненту ComboBoxFunc в инспекторе объектов.
o Перейдите на закладку Events (события) и найдите в ней событие
OnSelect.
o Дважды щелкните по полю справа. В поле должна появиться строка
ComboBoxFuncSelect – название обработчика по умолчанию.
Среда открывает окно кода и в нем появляется скелет метода
ComboBoxFuncSelect. Наберите в нем следующий код
void __fastcall TNonLinearEqTest::ComboBoxFuncSelect(TObject *Sender)
{
curLeftSide = DefLeftSide[ComboBoxFunc->ItemIndex];
curRightSide = DefRightSide[ComboBoxFunc->ItemIndex];
LEditLeft->Text=FloatToStr(curLeftSide);
LEditRight->Text=FloatToStr(curRightSide);
}
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
105
Теперь каждый раз при выборе новой функции нелинейного уравнения из
компоненты ComboBoxFunc будут обновляться значения интервала
изоляции и содержание редакционных окошек с границами этого
интервала.
 Для выполнения кода, следующего непосредственно за созданием формы, в
Delphi создавался обработчик OnCreate. В C++ Builder наберите аналогичный
код непосредственно в теле конструктора класса TNonLinearEqTest, которое у
нас пока пустое. Вот этот код вместе с заголовком конструктора
__fastcall TNonLinearEqTest::TNonLinearEqTest(TComponent* Owner)
: TForm(Owner)
{
LEditAbsAcc->Hint="Абсолютная погрешность >= "
+FormatFloat ("#.##e-##",MinAbsAcc);
LEditRelAcc->Hint=
"Относительная погрешность должна быть в интервале ["+
FormatFloat ("#.##e-##",MinRelAcc)+";"+
FormatFloat ("#.##e-##",MaxRelAcc)+"]";
ComboBoxMethod->ItemIndex=0;
ComboBoxFunc->ItemIndex=0;
ComboBoxFuncSelect (this);
State = waiting;
LEditAbsAcc->Text=FloatToStr(DefAbsAcc);
LEditRelAcc->Text=FloatToStr(DefRelAcc);
curAbsAcc=DefAbsAcc;curRelAcc=DefRelAcc;
}
 Теперь наберите обработчик клика кнопки ButtonCompute. Для этого можно
просто дважды щелкнуть по самой кнопке с надписью Считай в форме. Это
способ задания так называемого «обработчика по умолчанию». Появится
скелет обработчика. Поместите в него код, приводящий к вычислению
корня.
void __fastcall TNonLinearEqTest::ButtonComputeClick(TObject *Sender)
{
if (nle) delete nle;
switch (ComboBoxMethod->ItemIndex)
{
case 0:
nle= new TNonLinearEqBisection; break;
case 1:
nle= new TNonLinearEqSecant;
break;
case 2:
nle= new TNonLinearEqFalsePos; break;
case 3:
nle= new TNonLinearEqRidders;
break;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
106
case 4:
nle= new TNonLinearEqBrent;
break;
case 5:
nle= new TNonLinearEqNewton;
break;
}
nle->RelAcc=curRelAcc;
nle->AbsAcc=curAbsAcc;
nle->OnBeforeIterations=beforeIterations;
nle->OnIteration=onIteration;
nle->OnAfterIterations=afterIterations;
nle->LeftHandSide=f;
if (typeid(*nle)==typeid(TNonLinearEqNewton))
((TNonLinearEqNewton*)nle)->Derivative=df;
State=computing;
try
{
nle->GetRoot(curLeftSide,curRightSide);
}
__finally
{
delete nle; State=waiting;
}
}
 Наберите обработчики, фиксирующие ввод данных через окошки,
определяющие интервал изоляции и погрешности. В этом случае надо
присоединить один и тот же обработчик к событиям, поступающим от
разных объектов – четырех редакционных окошек ввода LEditLeft,
LEditRight, LEditAbsAcc и LEditRelAcc типа TLabeledEdit. Начните с события
OnExit, наступающего в момент переноса фокуса с редакционного окошка
на какой-либо другой объект интерфейса. Для этого
o Войдите в окно инспектора объектов и в верхней строке откройте
список всех объектов формы. Выберите в нем первый объект
LEditLeft. Перейдите на закладку Events и найдите событие OnExit. В
поле справа наберите имя LEditInExit (это, конечно, произвольное
имя), которое будет использовать обработчик событий выхода из
редакционных окошек. Нажмите Enter. Появится, как обычно, окно
кода со скелетом обработчика.
o Вновь войдите в окно инспектора объектов и найдите в верхнем
списке объект LEditRight. Затем, на страничке Event найдите опять
событие OnExit. Теперь в правом поле откройте список уже
имеющихся обработчиков нужного типа. Выберите обработчик с
именем LeditInExit, который только что был создан.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
107
o Повторите предыдущий пункт для объектов LEditAbsAcc и
LEditRelAcc.
Теперь у всех четырех окошек ввода будет один и тот же обработчик
выхода. В теле обработчика поместите строку кода, в которой вызывается
метод, проверяющий правильность вводимой информации.
void __fastcall TNonLinearEqTest::LEditInExit(TObject *Sender)
{
// Обратите внимание на синтаксис преобразования типа в языке C++
Validate((TLabeledEdit*)Sender);
}
 Пользователь может зафиксировать ввод нажатием клавиши Enter. Поэтому
следует предусмотреть соответствующий обработчик. Создайте один общий
обработчик события OnKeyPress с именем LeditInKeyPress для тех же самых
объектов – редакционных окошек ввода LEditLeft, LEditRight, LEditAbsAcc и
LEditRelAcc. В нем поместите строку вызова метода Validate при условии
нажатия на клавишу Enter.
void __fastcall TNonLinearEqTest::LEditInKeyPress(TObject *Sender,
char &Key)
{
// Обратите внимание на синтаксис преобразования типа в языке C++
if (Key==(char)13) Validate((TLabeledEdit*)Sender);
}
 Наконец, создайте обработчик события входа OnEnter в любой объект,
принадлежащий групповой компоненте GroupBoxIn. Для этого достаточно
написать обработчик входа в сам объект GroupBoxIn. В этом обработчике
следует очищать содержимое окошек вывода, в которых могли остаться
результаты предыдущего счета.
Опять войдите в окно инспектора объектов. Выберите в верхнем списке
объект GroupBoxIn. В окне Events найдите событие OnEnter и дважды
щелкните по правой клеточке. Наберите в появившемся скелете
обработчика следующий код
void __fastcall TNonLinearEqTest::GroupBoxInEnter(TObject *Sender)
{
ClearOut();
}
На этом, собственно, написание кода модуля формы завершается. Можно еще
раз проверить модуль, посмотрев код его файла заголовков и файла .cpp.
Проведите компиляцию проекта командой Build из меню Project. Все должно
пройти нормально. Запустите приложение на счет командой Run из меню
Project (зеленая стрелка). Проверьте работу программы в счете (runtime).
Попытайтесь изменить интервал изоляции, сделав его не верным, или вводите
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
108
произвольные символы в окошки ввода, чтобы проверить реакцию кода.
Опробуйте разные методы решения одного и того же уравнения. Посмотрите
число итераций. В общем, займитесь отладкой программы.
Вопросы для самоконтроля
1.
2.
3.
4.
5.
6.
7.
Как создается шаблон оконного приложения в C++ Builder?
Какова роль служебного слова __classid?
Что делает метод Application->ShowException?
Какой параметр имеет конструктор объектов класса TForm в C++ Builder?
Синтаксис описания постоянных массивов и перечислимого типа в C++.
Синтаксис описания конструктора формы в файле .cpp.
Прокомментируйте использование конструктора формы в C++ вместо
обработчика события OnCreate формы.
Среда MS Visual Studio
В следующих модулях будут создаваться классы и приложения для работы в
среде .NET (.NET Framework), а не в Win32, как в предыдущих модулях.
Особенность создаваемого в среде .NET кода в том, что он является
управляемым (managed) со стороны так называемого Общего Языка Времени
выполнения (Common Language Runtime, или CLR). Язык CLR обеспечивает
автоматическую сборку мусора (освобождение динамически занимаемой
объектами памяти, или «кучи»), поддержку безопасности кода и имеет ряд
других преимуществ. Кроме того, платформа .NET содержит объемную
библиотеку классов, которая нам будет заменять библиотеку VCL среды
CodeGear фирмы Borland.
C#
В языке C# компилируемый модуль является отдельным файлом или группой
файлов кода и содержит в себе сразу и описание, и реализацию методов класса.
Хэдеры отсутствуют. Последовательность описания членов класса не имеет
значения. Такой модуль легко компилируется в отдельный исполняемый модуль
с расширением .dll (dynamic link library). В отличие от exe-файла динамически
загружаемая библиотека не имеет точки входа и не может выполняться
независимо от вызывающего приложения.
Весь код в C# разбит на пространства имен. Обычно отдельный
компилируемый модуль относится к одному пространству имен, которое
указывается в заголовке модуля. Хотя это не правило.
В начале модуля может находиться список используемых пространств имен
(служебное слово using ставится перед каждым именем пространства имен в
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
109
списке). В нашем случае в этом списке есть только одно имя System.
Использование каких-либо идентификаторов из пространства имен System в
нашем модуле не потребует написания полного имени. Например, класс Math,
используемый в нашем пространстве имен NonLinEqDLL, описан в модуле System.
Использование директивы using System позволяет вызывать внутри модуля
класс Math его кратким именем, а не полным System.Math.
В языке C# все типы являются классами – наследниками одного общего для
всех класса Object. В частности это относится ко всем простым типам int,
double и т.д. Это так называемые типы-значения. К типам-значениям относится
также перечислимый тип enum. Другой распространенный тип классов
обозначается служебным словом delegate. Он позволяет описать обработчик
событий или указатель на любой метод класса. Из предыдущего контекста в
Delphi и C++ должен быть понятен смысл классов типа delegate.
Код классов на C#. Интерфейс-класс
Очень поучительно сравнить записанный ниже код с кодом в C++ и в Delphi.
using System;
namespace CSdllNonLinEq
{
// Тип делегата левой части уравнения f(x) = 0
// delegate обозначает специальный тип классов, содержащий ссылки на методы класса.
// В Delphi и C Builder с той же целью описывались типы ссылок на методы класса.
// При описании класса типа delegate указывается сигнатура методов, ссылки на которые
// он будет сохранять.
// Сигнатура метода объединяет в себе число и тип параметров и тип
// возвращаемого значения.
// В данном случае TLeftHandSide имеет один параметр типа double,
// и возвращает значение типа double
// Имя метода является именем типа делегата.
// Для внешнего кода данный делегат открыт (public)
// и имеет имя CSdllNonLinEq.TLeftHandSide.
public delegate double TLeftHandSide (double x);
// Тип делегата с обработчиками события при каждой итерации сужения
public delegate bool OnIterationEventHandler (object sender);
// Некий обобщенный тип делегата с обработчиками событий.
public delegate void anEventHandler (object sender);
// В отличие от Delphi и C++ Borland в описываемой здесь, на языке C##, иерархии классов
// присутствует особый тип класса – так называемый интерфейс-класс.
// Интерфейс представляет собой контракт, который должен быть выполнен
// любым классом-наследником. Это фактически полностью абстрактный класс, в котором
// не реализован ни один метод, свойство или событие.
// Реализовать указанные в интерфейсе члены должны классы-наследники.
/// <summary>
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
110
/// Интерфейс, определяющий совокупность свойств, событий и методов,
/// которыми должен обладать класс, реализующий алгоритм поиска корня
/// нелинейного уравнения на заданном интервале изоляции и с заданной погрешностью.
/// </summary>
public interface INonLinearEquation
{
/// <summary>
/// Устанавливает и возвращает метод, определяющий левую часть уравнения
/// </summary>
TLeftHandSide LeftHandSide { set; get;}
/// <summary>
/// Определяет метод обработки события на каждом шаге итерационного цикла
/// </summary>
event OnIterationEventHandler OnIteration;
/// <summary>
/// Определяет метод обработки события перед началом итерационного цикла.
/// </summary>
event anEventHandler OnBeforeIterations;
/// <summary>
/// Определяет метод обработки события после окончания итерационного цикла.
/// </summary>
event anEventHandler OnAfterIterations;
/// <summary>
/// Устанавливает и возвращает абсолютную погрешность определения корня
/// </summary>
double AbsAcc { set; get;}
/// <summary>
/// Устанавливает и возвращает относительную погрешность определения корня
/// </summary>
double RelAcc { set; get;}
/// <summary>
/// Возвращает текущее, приближенное значение корня
/// </summary>
double Root { get;}
/// <summary>
/// Определяет корень нелинейного уравнения
/// </summary>
/// <param name="aLeftSide">
/// Левая граница интервала изоляции
/// </param>
/// <param name="aRightSide">
/// Правая граница интервала изоляции
/// </param>
/// <returns>
/// Значение корня уравнения
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
111
/// </returns>
double GetRoot (double aLeftSide, double aRightSide);
}
Абстрактный класс NonLinearEquation
// Абстрактный класс решения нелинейного уравнения
// Обратите внимание, что в языке C# каждый член пространства имен и каждый член
// класса должен иметь модификатор доступа.
// Это относится и к заголовкам самих классов, чего нет в C++ и Delphi.
// По умолчанию класс без модификатора доступа считается internal – имеющий
// доступ только внутри сборки (в данном случае – внутри библиотеки классов).
// Члены класса, не имеющие модификатора доступа, считаются private – доступными
// только внутри класса.
// Как и в Delphi, класс, не имеющий предка, наследует от класса Object
/// <summary>
/// Абстрактный класс решения нелинейного уравнения f(x)= 0.
/// Наследует интерфейс InonLinearEquation.
/// </summary>
// Наследуя интерфейс, класс обязуется реализовать объявленные в интерфейсе методы,
// свойства, события.
public abstract class NonLinearEquation: INonLinearEquation
{
public const double
MinRelAcc = 1e-16,
// минимальная допустимая относительная погрешность
MaxRelAcc = 0.01,
// максимальная допустимая относительная погрешность
DefRelAcc = MinRelAcc, // относительная погрешность по умолчанию
MinAbsAcc = 1e-15,
// минимальная допустимая абсолютная погрешность
DefAbsAcc = MinAbsAcc;// абсолютная погрешность по умолчанию
// Члены, доступные только внутри класса (private по умолчанию)
// Поля
// Хранит ссылку на делегат с левой частью нелинейного уравнения
TLeftHandSide leftHandSide;
double root;
// хранит текущее значение корня
// Хранят значения аргумента и функции
// на левой и правой границе интервала изоляции
double leftSideArg, rightSideArg, leftSideFunc, rightSideFunc;
double delta;
// хранит текущую неопределенность в значении корня (>0)
double relAcc, absAcc;// хранят текущие погрешности
// События
// Событие OnBeforeIterations наступает сразу после вызова метода Initialize
// и перед входом в основной итерационный цикл.
// Обратите внимание на синтаксис описания события.
public event anEventHandler OnBeforeIterations;
// Событие OnAfterIterations наступает сразу после завершения
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
112
// основного итерационного цикла
public event anEventHandler OnAfterIterations;
// Событие OnIteration натсупает сразу после завершения каждой итерации,
// т.е. после вызова метода DoIteration
public event OnIterationEventHandler OnIteration;
// Методы
// Возвращает значение корня, организует основной итерационный цикл
double BasicLoop()
{
Initialize();
// Вызов обработчика перед началом цикла итераций
// Обратите внимание на использование события в коде.
if (OnBeforeIterations != null) OnBeforeIterations(this);
// Основной цикл
do { }
// Обратите внимание на синтаксис вызова метода с параметрами по ссылке
while (!(DoIteration(ref root, ref delta) ||
root > rightSideArg || root < leftSideArg ||
// окончание, стимулированное обработчиком onIteration
OnIteration != null && OnIteration(this) ||
// по погрешности
// Обратите внимание, что стандартная мат. функция Abs является
// статическим методом класса Math библиотеки .NET.
delta < 2.0 * relAcc * Math.Abs(root) + 0.5 * absAcc));
// Вызов обработчика после конца цикла итераций
if (OnAfterIterations != null) OnAfterIterations(this);
// Обратите внимание на синтаксис создания объекта исключительной ситуации.
// Сравните с Delphi и C++.
// Класс ArgumentOutOfRangeException описан в библиотеке .net.
if (root > rightSideArg || root < leftSideArg)
throw new
ArgumentOutOfRangeException ("Алгоритм покинул интервал изоляции!");
return root;
}
// Методы, доступные наследникам
// Метод Initialize инициализирует поля, используемые наследниками.
// В этом классе метод пустой
protected virtual void Initialize() { }
// Абстрактный метод выполнения одной итерации приближения к корню.
// Метод DoIteration реализует конкретный алгоритм поиска корня
// на одном шаге итерации.
// В настоящем классе является абстрактным и должен быть перекрыт наследником.
// Своими двумя параметрами по ссылке метод DoIteration должен вернуть
// текущие значения корня CurRoot и его неопределенность CurDelta (>0).
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
113
// Эти параметры используются в условии завершения итерационного цикла.
// Метод DoIteration вырабатывает и возвращает
// свое независимое от кода основного цикла
// условие завершения итерационного цикла значениями true илм false.
// Обратите внимание на синтаксис описания параметров по ссылке.
// Сравните с Delphi и C++.
protected abstract bool
DoIteration (ref double CurRoot, ref double CurDelta);
// Свойства
// Свойство AbsAcc возвращает и устанавливает текущее значение
// абсолютной погрешности.
// Обратите внимание на синтаксис описания свойства в C#.
// Сравните с Delphi и C++
// Здесь оба метода доступа set и get описываются вместе со своими телами
// и являются содержанием свойства.
// Параметр value метода set является служебным словом.
public double AbsAcc
{
set
{
absAcc = value < MinAbsAcc ? MinAbsAcc : value;
}
get { return absAcc; }
}
// Свойство возвращает и устанавливает текущее значение относительной погрешности
public double RelAcc
{
set
{
relAcc = value < MinRelAcc ? MinRelAcc : value > MaxRelAcc ?
MaxRelAcc : value;
}
get { return relAcc; }
}
// Свойство возвращает текущее значение интервала неопределенности корня
public double Delta { get { return delta; } }
// Свойство возвращает и устанавливает ссылку на делегата с левой частью уравнения
public TLeftHandSide LeftHandSide
{
get { return leftHandSide; }
set { leftHandSide = value; }
}
// возвращает значение аргумента на левой границе интервала изоляции
public double LeftSideArg { get { return leftSideArg; } }
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
114
// возвращает значение функции на левой границе интервала изоляции
public double LeftSideFunc { get { return leftSideFunc; } }
// возвращает значение аргумента на правой границе интервала изоляции
public double RightSideArg { get { return rightSideArg; } }
// возвращает значение функции на правой границе интервала изоляции
public double RightSideFunc { get { return rightSideFunc; } }
// возвращает текущее значение корня
public double Root { get { return root; } }
// Конструктор создает экземпляр класса с
// полями абсолютной и относительной погрешностей по умолчанию.
// Остальные поля класса конструктор обнуляет.
public NonLinearEquation()
{
AbsAcc = DefAbsAcc; RelAcc = DefRelAcc;
}
// Виртуальный метод GetRoot определяет и возвращает корень уравнения root
// на интервале изоляции [aLeftSideArg; aRightSideArg]
public virtual double GetRoot (double aLeftSideArg, double aRightSideArg)
{
// Обратите внимания на синтаксис создания объектов исключительной ситуации
if (leftHandSide == null)
throw new ArgumentNullException("Левая часть уравнения не определена!");
if (aLeftSideArg >= aRightSideArg)
throw new ArgumentOutOfRangeException("Левая граница больше правой!");
leftSideArg = aLeftSideArg; rightSideArg = aRightSideArg;
// Проверка значений на границе
leftSideFunc = leftHandSide(leftSideArg);
if (0 == leftSideFunc) return root = leftSideArg;
rightSideFunc = leftHandSide(rightSideArg);
if (0 == rightSideFunc) return root = rightSideArg;
// Проверка изолированности корня
if (leftSideFunc * rightSideFunc > 0.0)
throw new ArgumentException("Это не интервал изоляции!");
// Начальное значение корня
root = 0.5 * (rightSideArg + leftSideArg);
// Начальная неопределенность
delta = rightSideArg - leftSideArg;
//Основной цикл
return root = BasicLoop();
}
}
Классы NonLinearEqBisection и NonLinearEqSecant
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
115
// Класс решает уравнение f(x) = 0 методом деления отрезка пополам (Bisection)
public class NonLinearEqBisection: NonLinearEquation
{
// Поля хранят текущий аргумент, функцию и неопределенность корня
double curArg, curFunc, curDelta;
// Описание виртуального метода у наследника должно содержать спецификатор
// override, как в Delphi
protected override bool DoIteration(ref double CurRoot, ref double CurDelta)
{
double tempArg;
curFunc = LeftHandSide(CurRoot = (tempArg = curArg + (curDelta *= 0.5)));
CurDelta = Math.Abs(curDelta);
if (curFunc <= 0.0) curArg = tempArg;
return 0.0 == curFunc;
}
protected override void Initialize()
{
if (LeftSideFunc > 0.0)
{
curDelta = LeftSideArg - RightSideArg;
curArg = RightSideArg; curFunc = RightSideFunc;
}
else
{
curDelta = RightSideArg - LeftSideArg;
curArg = LeftSideArg; curFunc = LeftSideFunc;
}
}
}
// Класс решает уравнение f(x) = 0 методом секущих
public class NonLinearEqSecant: NonLinearEquation
{
// Поля текущих значений аргумента и функции последнего приближения
double lastArg, lastFunc,
// Поля текущих значений аргумента, функции и неопределенности корня
curArg, curFunc, curDelta;
protected override bool DoIteration(ref double CurRoot, ref double CurDelta)
{
curDelta = (lastArg - curArg) * curFunc / (curFunc - lastFunc);
lastArg = curArg; lastFunc = curFunc;
curFunc = LeftHandSide(CurRoot = curArg += curDelta);
CurDelta = Math.Abs(curDelta);
return 0.0 == curFunc;
}
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
116
protected override void Initialize()
{
if (Math.Abs(LeftSideFunc) < Math.Abs(RightSideFunc))
{
curArg = LeftSideArg; curFunc = LeftSideFunc;
lastArg = RightSideArg; lastFunc = RightSideFunc;
}
else
{
curArg = RightSideArg; curFunc = RightSideFunc;
lastArg = LeftSideArg; lastFunc = LeftSideFunc;
}
}
}
Классы NonLinearEqFalsePos и NonLinearEqRidder
// Класс решает уравнение f(x) = 0 методом ложного положения
public class NonLinearEqFalsePos: NonLinearEquation
{
// Поля, хранящие текущие значения аргумента и функции,
// отвечающих границам с отрицательным и положительным значениями функции
double negSideArg, posSideArg, negSideFunc, posSideFunc,
deltaArg, // Поле текущей разности значений аргумента на краях интервала
// Поля, хранящие текущую неопределенность корня
// и текущие значения аргумента и функции
curDelta, curArg, curFunc;
protected override bool DoIteration(ref double CurRoot, ref double CurDelta)
{
curFunc = LeftHandSide(curArg = negSideArg +
deltaArg * negSideFunc / (negSideFunc - posSideFunc));
if (curFunc < 0.0)
{
curDelta = negSideArg - curArg;
negSideArg = curArg; negSideFunc = curFunc;
}
else
{
curDelta = posSideArg - curArg;
posSideArg = curArg; posSideFunc = curFunc;
}
deltaArg = posSideArg - negSideArg;
CurRoot = curArg; CurDelta = Math.Abs(curDelta);
return 0.0 == curFunc;
}
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
117
protected override void Initialize()
{
if (LeftSideFunc < 0.0)
{
negSideArg = LeftSideArg; negSideFunc = LeftSideFunc;
posSideArg = RightSideArg; posSideFunc = RightSideFunc;
}
else
{
negSideArg = RightSideArg; negSideFunc = RightSideFunc;
posSideArg = LeftSideArg; posSideFunc = LeftSideFunc;
}
deltaArg = posSideArg - negSideArg;
}
}
// Класс решает уравнение f(x) = 0 методом Ридерса
public class NonLinearEqRidder: NonLinearEquation
{
// Поля, хранящие значения аргумента и функции
// на краях интервала изоляции корня и текущие значения аргумента и функции
double leftArg, rightArg, leftFunc, rightFunc, curArg, curFunc;
protected override bool DoIteration(ref double CurRoot, ref double CurDelta)
{
double midpointArgValue = 0.5 * (leftArg + rightArg),
midpointFuncValue = LeftHandSide(midpointArgValue),
temp = Math.Sqrt(midpointFuncValue * midpointFuncValue - leftFunc * rightFunc);
if (0.0 == temp) return true;
double newArg = midpointArgValue + (midpointArgValue - leftArg) *
(leftFunc >= rightFunc ? 1.0 : -1.0) * midpointFuncValue / temp;
if ((CurDelta = Math.Abs(curArg - newArg)) < AbsAcc)
{
CurRoot = curArg;
return true;
}
curFunc = LeftHandSide(curArg = newArg);
if (midpointFuncValue < 0.0 ^ curFunc < 0.0)
{
leftArg = midpointArgValue; leftFunc = midpointFuncValue;
rightArg = curArg; rightFunc = curFunc;
}
else
if (leftFunc < 0.0 ^ curFunc < 0.0)
{
rightArg = curArg; rightFunc = curFunc;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
118
}
else
if (rightFunc < 0.0 ^ curFunc < 0.0)
{
leftArg = curArg; leftFunc = curFunc;
}
CurRoot = curArg;
CurDelta = Math.Abs(rightArg - leftArg);
return 0.0 == curFunc;
}
protected override void Initialize()
{
leftArg = LeftSideArg; rightArg = RightSideArg;
leftFunc = LeftSideFunc; rightFunc = RightSideFunc;
// Обратите внимание на то, что у класса Double есть константа NaN
curArg = Double.NaN;
}
}
Классы NonLinearEqBrent и NonLinearEqNewton
// Класс решает уравнение f(x) = 0 методом Брента
public class NonLinearEqBrent: NonLinearEquation
{
// Поля, хранящие текущие значения аргумента и функции,
// в трех точках интервала изоляции корня
double firstPointArg, firstPointFunc,
secondPointArg, secondPointFunc,
thirdPointArg, thirdPointFunc,
// и текущие значения разделяющих эти точки интервалов
firstDelta, secondDelta, tempDelta;
protected override bool DoIteration(ref double CurRoot, ref double CurDelta)
{
double numerator, denominator, aFraction, bFraction, aMin, bMin, CurAcc;
if (secondPointFunc * thirdPointFunc > 0.0)
{
thirdPointArg = firstPointArg; thirdPointFunc = firstPointFunc;
firstDelta = secondPointArg - firstPointArg; secondDelta = firstDelta;
}
if (Math.Abs(thirdPointFunc) < Math.Abs(secondPointFunc))
{
firstPointArg = secondPointArg; secondPointArg = thirdPointArg;
thirdPointArg = firstPointArg;
firstPointFunc = secondPointFunc; secondPointFunc = thirdPointFunc;
thirdPointFunc = firstPointFunc;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
119
}
tempDelta = 0.5 * (thirdPointArg - secondPointArg);
CurAcc = 2.0 * RelAcc * Math.Abs(secondPointArg) + 0.5 * AbsAcc;
if ((CurDelta = Math.Abs(tempDelta)) < CurAcc || 0.0 == secondPointFunc)
{
CurRoot = secondPointArg;
return true;
}
if (Math.Abs(secondDelta) >= CurAcc &&
Math.Abs(firstPointFunc) > Math.Abs(secondPointFunc))
{
bFraction = secondPointFunc / firstPointFunc;
if (firstPointArg == thirdPointArg)
{
numerator = 2.0 * tempDelta * bFraction; denominator = 1.0 - bFraction;
}
else
{
denominator = firstPointFunc / thirdPointFunc;
aFraction = secondPointFunc / thirdPointFunc;
numerator = bFraction * (2.0 * tempDelta * denominator * (denominator - aFraction)
- (secondPointArg - firstPointArg) * (aFraction - 1.0));
denominator = (denominator - 1.0) * (aFraction - 1.0) * (bFraction - 1.0);
}
if (numerator > 0.0) denominator = -denominator;
numerator = Math.Abs(numerator);
aMin = 3.0 * tempDelta * denominator - Math.Abs(CurAcc * denominator);
bMin = Math.Abs(secondDelta * denominator);
if (2.0 * numerator < (aMin < bMin ? aMin : bMin))
{
secondDelta = firstDelta; firstDelta = numerator / denominator;
}
else
{
firstDelta = tempDelta; secondDelta = firstDelta;
}
}
else
{
firstDelta = tempDelta; secondDelta = firstDelta;
}
firstPointArg = secondPointArg; firstPointFunc = secondPointFunc;
if (Math.Abs(firstDelta) > CurAcc) secondPointArg += firstDelta;
else secondPointArg += Math.Sign(tempDelta) * CurAcc;
secondPointFunc = LeftHandSide(secondPointArg);
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
120
CurRoot = secondPointArg;
return 0.0 == secondPointFunc;
}
protected override void Initialize()
{
tempDelta = (thirdPointArg = secondPointArg = RightSideArg) –
(firstPointArg = LeftSideArg);
firstPointFunc = LeftSideFunc;
thirdPointFunc = secondPointFunc = RightSideFunc;
}
};
// Класс решает уравнение f(x) = 0 методом Ньютона-Рафсона
public class NonLinearEqNewton : NonLinearEquation
{
// Поля, хранящие текущие значения аргумента
// на краях интервала изоляции корня
double lowArg, highArg,
// Поля, хранящие текущую и прешествующую неопределенность корня
deltaArg, prevDeltaArg,
// Поля, хранящие текущие значения аргумента, функции и ее производной
curArg, curFunc, curDerivative;
// Поле, хранящее указатель на функцию, определяющую производную функции f(x)
TLeftHandSide derivative;
// возвращает и устанавливает производную
public TLeftHandSide Derivative
{
get { return derivative; }
set { derivative = value; }
}
protected override bool DoIteration(ref double CurRoot, ref double CurDelta)
{
double tempArg;
if (((curArg - highArg) * curDerivative - curFunc) *
((curArg - lowArg) * curDerivative - curFunc) > 0 ||
Math.Abs(2.0 * curFunc) > Math.Abs(prevDeltaArg * curDerivative))
{
prevDeltaArg = deltaArg;
deltaArg = 0.5 * (highArg - lowArg);
curArg = lowArg + deltaArg;
if (lowArg == curArg)
{
CurRoot = curArg; CurDelta = Math.Abs(deltaArg); return true;
}
}
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
121
else
{
prevDeltaArg = deltaArg;
deltaArg = curFunc / curDerivative;
tempArg = curArg;
curArg -= deltaArg;
if (curArg == tempArg)
{
CurRoot = curArg; CurDelta = Math.Abs (deltaArg); return true;
}
}
CurDelta = Math.Abs (deltaArg);
CurRoot = curArg;
if (CurDelta < 2.0 * RelAcc * Math.Abs(curArg) + 0.5 * AbsAcc) return true;
curFunc = LeftHandSide(curArg);
curDerivative = derivative(curArg);
if (curFunc < 0.0) lowArg = curArg; else highArg = curArg;
return 0.0 == curFunc;
}
protected override void Initialize()
{
if (LeftSideFunc < 0.0)
{
lowArg = LeftSideArg; highArg = RightSideArg;
}
else
{
lowArg = RightSideArg; highArg = LeftSideArg;
}
curArg = Root;
deltaArg = prevDeltaArg = Delta;
curFunc = LeftHandSide(curArg);
curDerivative = derivative(curArg);
}
// Метод GetRoot для метода Ньютона-Рафсона
public override double GetRoot(double aLeftSideArg, double aRightSideArg)
{
// проверяется наличие производной функции f(x)
if (derivative == null)
throw new ArgumentNullException("Производная не задана!");
// Обратите внимание на синтаксис вызова виртуального метода предка
// Вместо дельфийского inherited используется служебное слово base
return base.GetRoot (aLeftSideArg, aRightSideArg);
}
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
122
}
}
Обратите внимание на комментарий, помещенный после тройного обратного
слэша ///. Это особый вид комментария, включающий в себя тэги языка xml. По
этим тэгам среда определяет содержание информации, относящейся к
комментируемому элементу. Комментируемым элементом может быть сам
класс и любой из его членов. Тэг <summary> означает, например, что между ним и
отрицающим его тэгом </summary> находится текст, кратко описывающий
назначение комментируемого элемента. Другой тэг <param> ограничивает
область информации о параметрах метода и т.п.
Создание библиотеки классов
Приведенный выше код будет помещен в динамически связываюмую
библиотеку.
Для этого
 Откройте среду MS Visual Studio.
 В меню File по команде New->Project… откройте окно New Project.
 На панели Project Types в узле Other Project Types выберите элемент Visual
Studio Solutions.
 Выделите элемент Blank Solution и наберите имя solCSNonLinEq в строке
Name. Нажмите Enter.
 Откройте окно Solution Explorer. Это окно подобно Project Manager в Delphi.
Solution (решение) подобно Project Group является логическим контейнером
для будущих приложений, которые будут тестировать классы решения
нелинейных уравнений.
 В контекстном меню на строке solCSNonLinEq выберите команду Add->New
Project….
 В открывшемся окне Add New Project выберите Class Library (библиотека
классов) среди предлагаемых шаблонов (templates).
 Дайте создаваемой библиотеке имя CSdllNonLinEq. Нажмите Enter.
Появится шаблон файла с пространством имен CSdllNonLinEq. В списках
using можете убрать две строки
using System.Collections.Generic;
using System.Text;
Эти строки не мешают, но и библиотеки, которые в них заявлены, не
понадобятся в нашем коде.
 Уберите так же скелет класса Class1, который предложила среда. На его
место поместите код, описанный выше.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
123
 Скомпилируйте библиотеку командой Build CSdllNonLinEq из главного меню
Build среды. Результат должен быть положительный. В стандартном
каталоге Projects среды Visual Studio 2005 (каталог Visual Studio 2005 среда
открывает при своей установке в каталоге Мои документы рабочего профиля
пользователя) должен открыться каталог с решением solCSNonLinEq. Внутри
этого каталога среда создает ряд подкаталогов, в одном из которых
находится файл скомпилированой библиотеки CSdllNonLinEq.dll и файл с
только что отредактированым кодом CSdllNonLinEq.cs.
Консольное приложение
Составьте консольное приложение, тестирующее классы решения нелинейного
уравнения. Для этого
 Командой Add->New Project… в контекстном меню решения solCSNonLinEq (в
окне Solution Explorer) найдите шаблон Console Application и дайте ему имя
cnslCsNonLinEqTest. В результате среда создаст скелет консольного
приложения вида
using System;
using System.Collections.Generic;
using System.Text;
namespace cnslCsNonLinEqTest
{
class Program
{
static void Main (string [ ] args)
{
}
}
}
 Измените имя класса Program, созданого средой по умолчанию, на имя
cnslNonLinEqTest. Лучше всего менять имена командой Rename… из главного
меню среды Refactor.
В языке C#, как и в C++, функция Main является точкой входа в программу.
Собственно программа это и есть функция Main. Завершение функции Main есть
завершение программы.
В языке C# функция Main является методом класса. В данном случае это
объявленный класс cnslNonLinEqTest. Модификатор static означает, что
функция Main является методом именно самого класса, в котором она
объявлена, а не методом любого объекта этого класса. Вообще любой член
класса с модификатором static может быть вызван только с помощью имени
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
124
класса. Поля класса static существуют только в одном экземпляре. Любой
статический метод может использовать только статические поля своего класса.
Для работы приложения, тестирующего классы решения нелинейного
уравнения необходимо сослаться в приложении на библиотеку классов
CSdllNonLinEq, построенную в предыдущем разделе. Для этого
 В окне Solution Explorer найдите раздел References (ссылки) консольного
приложения cnslCsNonLinEqTest.
 Правой кнопкой мышки откройте контекстное меню и выберите в нем
команду Add Reference….
 В открывшемся окне Add Reference перейдите на закладку Project и
выберите библиотеку CSdllNonLinEq. Библиотека должна оказаться в списке
References приложения cnslCsNonLinEqTest.
 К списку using в начале файла приложения cnslCsNonLinEqTest добавьте
строку
using CSdllNonLinEq;
После этого все классы с модификатором доступа public пространства имен
CSdllNonLinEq будут доступны консольному приложению.
Библиотеки
using System.Collections.Generic;
using System.Text;
не понадобятся в нашем коде, и эти строки можно убрать.
Наберите теперь тело метода Main в форме, аналогичной Delphi и C++
nonLinEqTest nlTest = new nonLinEqTest ();
do { }
while ( nlTest.JustDoIt() );
В C# конструктор имеет имя класса, как это принято в С. Вызывается он
оператором new, создавая ссылку на объект. В отличие от Delphi нет
необходимости явно освобождать память от объекта после его использования. В
языке C# «очистка мусора» (garbage collection) производится автоматически,
если, конечно, объект не использует ресурсы, такие как файл, связь по сети и
т.п., которые требуют явного освобождения.
Наберите теперь содержание нового класса с именем nonLinEqTest внутри класса
cnslNonLinEqTest консольного приложения. Смысл членов класса nonLinEqTest
аналогичен тем, что использовались в Delphi и C++. Важным отличием является
то, что в C# классы можно объявлять внутри других классов. Это так
называемое вложенное (nested) описание типов, характерное для C#.
Вот так будет выглядеть код нового класса nonLinEqTest.
// Уровень доступа класса nonLinEqTest private.
// Этот класс вложен (nested) внутрь класса cnslNonLinEqTest
// По умолчанию его собственный доступ private.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
125
// То есть он виден только из содержащего его класса cnslNonLinEqTest.
// По умолчанию доступ его членов так же private.
// То есть, члены private вложенного класса видны
// только его собственным методам, но не извне.
// Они не видны даже методам содержащего его класса cnslNonLinEqTest
class nonLinEqTest
{
// Массив классов (ссылок на VMT), реализующих различные
// методы решения нелинейного уравнения.
// Класс Type является стандартным и описан в библиотеке System
// Обратите внимание на синтаксис описания массива и инициализацию
// его элементов в C#.
// Стандартная функция typeof возвращает тип (ссылку на VMT) своего аргумента
Type [ ] nonLinEqClasses = new Type [6] {typeof (NonLinearEqBisection),
typeof (NonLinearEqBrent), typeof (NonLinearEqFalsePos),
typeof (NonLinearEqNewton), typeof (NonLinearEqRidder),
typeof (NonLinearEqSecant)};
/// <summary>
/// Хранит номер уравнения
/// </summary>
int item;
/// <summary>
/// Хранит число итераций
/// </summary>
int itr;
// Инициализация последовательности случайных чисел
/// <summary>
/// Хранит ссылку на объект, порождающий случайные числа.
/// Инициализация этого объекта и инициализация последовательности
/// случайных чисел происходит при вызове конструктора класса.
/// </summary>
Random rnd = new Random ();
/// <summary>
/// Вычисляет левую часть уравнения
/// </summary>
/// <param name="x">
/// Аргумент
/// </param>
/// <returns>
/// Значение левой части
/// </returns>
double f (double x)
{
switch ( item )
{
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
126
// В C# математические функции являются статическими методами
// библиотечного класса Math.
case 0: return Math.Sin (x);
case 1:
if (x>1.0 || x==0.0)
throw EArgumentOutOfRangeException
("Аргумент вышел из области определения!");
return Math.Tan (x) - Math.Sqrt (1.0 - x * x) / x;
// Обратите внимание, что в C# NaN является статической постоянной
// класса Double.
default: return Double.NaN;
}
}
/// <summary>
/// Вычисляет производную левой части уравнения
/// </summary>
/// <param name="x">
/// Аргумент
/// </param>
/// <returns>
/// Значение производной
/// </returns>
double fder (double x)
{
switch ( item )
{
case 0: return Math.Cos (x);
case 1:
if (x>=1.0 || x==0.0)
throw EArgumentOutOfRangeException
("Аргумент вышел из области определения!");
return 1.0 / Math.Cos (x) / Math.Cos (x)
+ 1.0 / Math.Sqrt (1.0 - x * x) / x / x;
default: return Double.NaN;
}
}
/// <summary>
/// Обработчик на каждой итерации
/// </summary>
/// <param name="sender">
/// Вызывающий объект
/// </param>
/// <returns>
/// true, если процесс должен быть остановлен,
/// false - процесс должен быть продолжен
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
127
/// </returns>
bool onIteration (Object sender)
{
itr++;
return false;
}
/// <summary>
/// Выделяет случайный класс решения нелинейного уравнения.
/// выделяет случайное уравнение,
/// вводит интервал изоляции с консоли,
/// определяет корень уравнения,
/// выводит результат на консоль.
/// </summary>
/// <returns>
/// true, если введен символ "x", и false в любом другом случае
/// </returns>
internal bool JustDoIt ()
{
// Создается объект случайно выбранного из массива nonLinEqClasses класса.
// Это делает статический метод CreateInstance
// библиотечного класса Activator.
// Обратите внимание,
// что типом объекта nleq является интерфейс, как общий предок
// В то же время создается экземпляр конкретного класса-наследника
INonLinearEquation nleq =
(INonLinearEquation)Activator.CreateInstance
(nonLinEqClasses [rnd.Next (6)]);
// Событию "перед началом итераций" объекта nleq передается
// явным образом содержание короткого метода-обработчика.
// Обратите внимание на синтаксис делегирования обработчика событию
// явной передачей содержания короткого метода.
nleq.OnBeforeIterations += delegate { itr = 0; };
// Событию "на каждой итерации" объекта nleq передается
// метод onIteration с помощью делегата типа OnIterationEventHandler.
// Обратите внимание на синтаксис делегирования обработчика
// путем создания экземпляра класса делегата с указанием в качестве
// параметра конструктора ссылки на метод-обработчик.
nleq.OnIteration += new OnIterationEventHandler (onIteration);
// Событию "после окончания итераций" объекта nleq передается
// явным образом содержание короткого метода-обработчика
nleq.OnAfterIterations += delegate
// Обратите внимание на метод печати на экране writeLine.
// Он является статическим методом библиотечного класса Console.
// Так же посмотрите, как формируется строка, выводимая на печать.
// Взятое в фигурные скобки целое число, начиная от 0,
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
128
// нумерует элементы списка, перечисляемые далее через запятую.
// Элементы этого списка - распечатываемые переменные.
// Сравните с Delphi.
{ Console.WriteLine ("iterations = {0}", itr); };
// Задается случайное уравнение
item = rnd.Next (2);
try
{
// Делегируется левая часть уравнения
nleq.LeftHandSide = new TLeftHandSide (f);
// Делегируется производная для метода Ньютона-Рафсона
// Обратите внимание на синтаксис сравнения типов в C#
// с помощью метода GetType, унаследованного от Object
if ( nleq is NonLinearEqNewton )
// возможен другой вариант сравнения типов
//if ( nleq.GetType () == typeof (NonLinearEqNewton) )
(nleq as NonLinearEqNewton).Derivative = new TLeftHandSide (fder);
// Возможен другой вариант преобразования типов
// ((NonLinearEqNewton)nleq).Derivative = new TLeftHandSide (fder);
// Печатается тип метода и уравнение
// Обратите внимание на метод ToString,
// возвращающий строку, описывающую любой объект,
// вызывающий этот метод.
// escape-последовательность \n вызывает перенос строки.
Console.WriteLine ("\n" + nleq.GetType ().ToString () + "\n" +
"Equation: " +
(item == 0 ? "sin(x) = 0" : "tn(x)-sqrt(1.0-x*x)/x = 0"));
// Вводятся границы интервала изоляции
Console.Write ("Enter left side:");
// Обратите внимание на использование статического метода Parse
// класса Double для превращения строки в число
double leftSide = Double.Parse (Console.ReadLine ());
Console.Write ("Enter right side:");
double rightSide = Double.Parse (Console.ReadLine ());
try
{
// Вычисляется корень на заданном интервале
Console.WriteLine ("root: {0}",
nleq.GetRoot (leftSide, rightSide));
}
catch ( Exception e )
{
Console.WriteLine (e.Message);
}
}
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
129
catch ( Exception e )
{
Console.WriteLine (e.Message);
}
Console.Write ("Enter any key to continue; x - to end.");
return Console.ReadLine () != "x";
}
}
Текстовой вариант всего модуля cnslNonLinEqTest.cs консольного приложения
можно найти по этой ссылке.
Прежде чем запускать приложение командой Start Debugging из меню Debug,
убедитесь, что в окне Solution Explorer имя cnslCsNonLinEqTest выделено
полужирным шрифтом. Если это не так, то в контекстном меню на этом имени
выберите команду Set as StartUp Project. Тогда команда активизации будет
запускать именно проект cnslCsNonLinEqTest.
Оконное приложение в C#
Для создания оконного приложения
 Войдите в окно Solution Explorer.
 На узле Solution solCSNonLinEq вызовите контекстное меню и командой
Add -> New Project… откройте окно New Project.
 На центральной панели окна выберите шаблон Windows Application и в
строке Name наберите имя wNonLinEqTest.
В результате будет создан шаблон оконного приложения с одним окном. На
экране появится строительная площадка формы.
В приложении есть три файла с кодом
 файл самого приложения с именем Program.cs, который можно найти в окне
Solution Explorer. Щелкните по этому имени и посмотрите на содержание
файла приложения
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace wNonLinEqTest
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main ()
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
130
{
Application.EnableVisualStyles ();
Application.SetCompatibleTextRenderingDefault (false);
Application.Run (new Form1 ());
}
}
}
Содержание файла во многом похоже на содержание аналогичных файлов в
Delphi и C Builder. Есть некоторые особенности, характерные для C# и для
среды MS Visual Studio.
 Созданный средой по умолчанию класс Program объявлен с модификатором
static. Это означает, что в классе Program могут быть объявлены только
статические члены и что экземпляры класса Program не могут быть созданы.
 Перед объявлением знакомого нам статического метода Main кроме
комментария находится так называемый атрибут [STAThread]. На данном
этапе освоения языка не будем вникать в смысл этого конкретного атрибута
и классов атрибутов вообще. Отметим лишь, что атрибуты существуют для
того, чтобы особым образом пометить класс или его член для внешнего
кода.
 В методе Main стоит обращение класса Application к трем его статическим
методам. Первые два метода устанавливают параметры вывода окон
приложения на экран. Они не существенны в прикладном смысле и на них
не стоит акцентировать внимание. Третий метод – знакомый нам метод Run,
играет ту же роль, что в Delphi и C++ Builder. В данном случае у метода Run
один параметр типа объекта стандартного библиотечного класса Form.
Фактическим параметром является объект класса Form1. Он порождается
явным вызовом его конструктора по умолчанию new Form1 ().
 В модуле приложения не указана ссылка на модуль формы, как это было в
Delphi и C Builder. Дело в том, что модуль приложения и модуль формы
принадлежат одному и тому же пространству имен, названому нами
wNonLinEqTest. Чтобы убедиться в этом, откройте модуль с кодом формы.
Для этого в окне Solution Explorer щелкните по имени Form1.cs, или перейдите
на уже открытое окно со строительной площадкой формы, а затем через
контекстное меню или главное меню View вызовите командой View Code
кодовое содержание модуля окна. Вот что появится
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
131
using System.Text;
using System.Windows.Forms;
namespace wNonLinEqTest
{
public partial class Form1 : Form
{
public Form1 ()
{
InitializeComponent ();
}
}
}
Здесь
 Список библиотек, используемых в коде, перечисляется с директивами
using. Объявление каждой библиотеки должно сопровождаться отдельной
директивой using.
 Файл проекта и файл формы объединены общим пространством имен
wNonLinEqTest. В файле формы объявлен класс с именем Form1, наследующий
от библиотечного класса Form.
 Класс Form1 имеет доступ public и еще один спецификатор partial,
которого не было ни в Delphi, ни в C Builder.
 Внутри класса Form1 описан только его конструктор public Form1 () с
доступом public.
 В теле конструктора вызывается метод InitializeComponent, описание которого
в данном файле нет. Это связано со смыслом спецификатора partial
(частичный).
Дело в том, что в языке C# описание класса может располагаться не в одном, а
в нескольких физических файлах. В этом случае описание класса разбивается
на части, и описание каждой из частей должно начинаться спецификатором
partial. В данном случае одна из двух частей класса Form1 описана в файле
Form1.Designer.cs, который можно увидеть, щелкнув по его имени в окне Solution
Explorer. Редактировать эту часть описания класса Form1 не желательно без
веских на то оснований. Редакцией этой части описания класса Form1
занимается дизайнер среды. Файл Form1.Designer.cs играет роль, подобную
файлу формы с расширением .dfm в Delphi и C Builder.
В файле Form1.Designer.cs описан метод InitializeComponent. Этот метод можно
увидеть, если открыть область (region) кода с именем Windows Form Designer
generated code («код, генерируемый дизайнером формы окна»), взятым в рамку.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
132
Открывается такая поименованная область кода щелчком по знаку + на левом
поле строки.
Разбиение всего кода на отдельные области с именами значительно упрощает
работу программиста с большими кодовыми файлами. Разбиение кода на
области не имеет никакого отношения к содержательной стороне кода и
является просто способом логического структурирования кода.
Итак, окна Form1.Designer.cs и Program.cs можно закрыть, т.к. редактировать их в
дальнейшем не придется. Во время визуального конструирования содержание
этих файлов будет меняться дизайнером. Пока что, как и в случаях Delphi и C
Builder, для визуального конструирования формы проекта wNonLinEqTest
откройте окно со строительной площадкой Form1.cs [Design].
Проведите последовательно действия, аналогичные тем, что делались в Delphi
и C Builder при визуальном конструировании. Учтите, что визуальные
компоненты (контролы) в среде MS Visual Studio находятся в окне Toolbox, а
окно свойств (Properties) соответствует Object Inspector в Delphi и открывается
из меню View, как, впрочем, и все другие окна.
 В начале измените имя файла формы. Это лучше сделать в окне Solution
Explorer. Щелкните правой кнопкой по имени файла Form1.cs и в
контекстном меню выберите команду Rename. Введите новое имя
fNonLinEqTest.
 Теперь измените имя класса формы. Для этого войдите в окно дизайнера
fNonLinEqTest.cs [Design] и щелкните по строительной площадке формы.
Затем войдите в окно Properties, найдите свойство Name и измените его
значение на NonLinEqTest.
 Подключите к проекту wNonLinEqTest библиотеку классов CSdllNonLinEq,
построенную в предыдущем разделе.
Для этого
o В окне Solution Explorer найдите раздел References (ссылки)
консольного приложения wNonLinEqTest.
o Правой кнопкой мышки откройте контекстное меню и выберите в
нем команду Add Reference….
o В открывшемся окне Add Reference перейдите на закладку Project и
выберите библиотеку CSdllNonLinEq. Библиотека должна оказаться в
списке References приложения wNonLinEqTest.
o К списку using в начале файла формы fNonLinEqTest добавьте строку
using CSdllNonLinEq;
o Добавьте ссылку еще на одну библиотеку using System.Threading,
которая понадобится в коде.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
133
После этого все классы с модификатором доступа public пространства
имен CSdllNonLinEq будут доступны оконному приложению.
Визуальное проектирование
Теперь перейдите к визуальному проектированию формы.
 В начале расширьте немного границы окна (потяните за края вправо и вниз).
По умолчанию оно слишком мало. Поместите на него из окна Toolbox
компоненту класса GroupBox из раздела Containers.
 В окне Properties измените его имя (свойство Name) на groupBoxIn, а значение
свойства Text на In. (В библиотеке .NET и, в частности, в C# дельфийскому
свойству Caption компонент отвчает свойство Text).
 Перенесите на форму из Toolbox еще один объект класса GroupBox. Назовите
новый контрол groupBoxOut, а его свойству Text дайте значение Out.
 Наполните GroupBoxIn компонентами из окна Toolbox, которые понадобятся
для ввода информации в приложение:
o «Метка», или компонента класса Label находится в разделе Common
Controls. Поместите ее в верхнюю левую часть groupBoxIn. Назовите
ее labelMethod, а свойству Text дайте значение Method.
o Возьмите вторую метку типа Label. Поместите ее в верхнюю правую
часть groupBoxIn. Назовите labelFunc, а свойству Text дайте значение
Функция.
o Под левой меткой Method поместите компоненту класса ComboBox из
раздела Common Controls окна Toolbox. Назовите comboBoxMethod. В
нем будет располагаться список методов решения нелинейного
уравнения. Найдите свойтсво Items в окне Properties и щелкните
справа по кнопке с тремя точками. Должно открыться окно String
Collection Editor, в поле которого наберите список методов решения
уравнения следующего содержания
Bisection
Secant
False Position
Ridders'
Brent's
Newton's
o Этот список должен появляться при щелчке по правой кнопке
контрола comboBoxMethod. Свойство DropDownStyle в окне Properties
измените на DropDownList.
o Еще один объект класса ComboBox поместите симметрично под
меткой Функция. Назовите его comboBoxFunction. В нем будет список
функций нелинейных уравнений. Внесите в свойство Items список
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
134
sinx
tg(x)-x/sqrt(1-x^2)
o Измените свойство DropDownStyle на DropDownList.
o В разделе Common Controls окна Toolbox выберите компоненту
TextBox. Это соответствует классу редакционных окошек TEdit в
Delphi. Компоненты, состоящей из редакционного окошка и метки
типа TLabeledEdit, в палитре Toolbox нет. Перенесите TextBox на
форму в groupBoxIn и поместите ниже контрола groupBoxMethod.
Назовите textBoxLeft. В ней будет помещаться число – левая граница
интервала изоляции корня.
o В разделе Common Controls окна Toolbox выберите компоненту
ToolTip и перенесите ее на форму. Эта компонента будет носителем
текстов, которые появляются при наведении курсора мышки на
компоненты интерфейса окна. Назовите компоненту toolTip.
Обратите внимание, что в библиотеке .NET в отличие от VCL у
компонент нет свойства Hint. Зато существует отдельная компонента
класса ToolTip, которую можно поместить на форму в одном
экземпляре и связать с любым количеством контролов, придав свое
значение комментирующей строке, появляющейся при наведении
курсора мышки на каждый контрол.
o Теперь у компоненты textBoxLeft в окне Properties появилось
свойство ToolTip on toolTip. Дайте ему значение в виде строки Левая
граница должна быть меньше правой!. Окошко с этой надписью будет
появляться на экране, когда пользователь наведет указатель мышки
на контрол textBoxLeft.
o Поместите слева от редакционного окошка textBoxLeft метку типа
Label. Дайте ей имя labelLeft и свойству Text дайте значение LeftSide=.
o Такую же компоненту типа TextBox поместите чуть правее, на одной
высоте с предыдущим окошком. Назовите ее textBoxRight. Через это
окошко будет вводиться правая граница интервала изоляции корня.
o Установите свойству ToolTip on toolTip значение Правая граница должна
быть больше левой!.
o Аналогично предыдущему контролу поместите слева метку типа
Label. Назовите ее labelRight. Свойству Text метки дайте значение
RightSide=.
o В левой части groupBoxIn чуть ниже textBoxLeft поместите новую
компоненту типа GroupBox. В этой компоненте будут расположены
редакционные окошки, в которых будут задаваться погрешности
решения нелинейного уравнения. Поэтому назовите ее groupBoxAcc, а
свойству Text дайте значение Погрешности.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
135
o Внутрь groupBoxAcc поместите два редакционных окошка типа
TextBox. Назовите их textBoxAbsAcc и textBoxRelAcc соответственно.
Через эти окошки будут вводиться абсолютная и относительная
погрешности.
o Над ними поместите по одной метке с именами labelAbsAcc и
labelRelAcc соответственно. Свойствам Text меток дайте значения
Абсолютная и Относительная.
o Внутрь groupBoxIn, но вне groupBoxAcc, справа от последнего
поместите кнопку (объект типа Button). Назовите ее buttonCompute, а
свойству Text дайте значение Считай. Эта кнопка будет инициировать
процесс счета.
Теперь необходимо заселить groupBoxOut.
 Поместите на groupBoxOut четыре компоненты типа TextBox в два столбца по
паре и назовите их textBoxRoot, textBoxRootFunc, textBoxDelta и textBoxIterations
соответственно.
 Свойствам ReadOnly этих компонент придайте значение true. В эти
редакционные окошки программа будет выводить результаты счета и их
редактирование не имеет смысла.
 Снабдите каждое из этих 4-ех окошек метками типа Label, назвав их
соответственно labelRoot, labelRootFunc, labelDelta и labelIterations. Свойства Text
этих меток установите соответственно в Корень, Функция в корне,
Неопределенность, Число итераций.
В заключение
 поместите на форму компоненту StatusStrip из раздела Menus & ToolBars. В
ней будет сообщаться о текущем состоянии программы. Дайте ей имя
statusStrip. Эта компонента является контейнером для других компонент.
Поместите в этот контейнер метку. Для этого
o В свойствах компоненты statusStrip найдите Items. Щелкните по
кнопке с многоточием. Это откроет окно Items Collection Editor.
o В нем выберите (в списке слева вверху) компоненту типа StatusLabel
и добавьте ее к statusStrip, нажав кнопку Add.
o На правой панели откроется редактор свойств новой компоненты.
Имя (Name) измените на statusLabel, а значение свойства Text сотрите.
Это свойство будет меняться динамически при работе программы.
 Поместите компоненту ErrorProvider из раздела Components. Назовите ее
errorProvider. Эта компонента поможет при анализе ввода строки из окошек
ввода, реагируя на неверный ввод числа специальным предупреждением.
Объекты класса ErrorProvider имеют метод SetError, устанавливающий
содержание строки сообщения об ошибке в заданном контроле. Если эта
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
136
строка не пустая, то вызов метода SetError приводит к появлению на экране
рядом с контролом небольшого мигающего значка с изображением
восклицательного знака. При наведении курсора на этот значок появляется
строка сообщения об ошибке, переданная методу SetError.
Код членов класса формы
Теперь наберите код членов класса формы, который будет определять ее
поведение.
В начале добавьте в описание класса необходимые поля, аналогичные тем, что
использовались в проектах на Delphi и C Builder.
/// <summary>
/// Определяет тип состояния процесса
/// </summary>
enum TState { waiting, computing };
/// <summary>
/// Хранят левую и правую границу интервала изоляции по умолчанию
/// для двух уравнений.
/// Модификатор readonly означает, что эти поля можно только считывать,
/// но не изменять.
/// </summary>
readonly double [ ] defLeftSide = new double [2] { -2.5, 0.1 },
defRightSide = new double [2] { 3.0, 0.9 };
/// <summary>
/// Хранит ссылки на VMT классов решения нелинейного уравнения.
/// </summary>
Type [ ] nonLinearEqClasses = new Type [6]
{typeof (NonLinearEqBisection), typeof (NonLinearEqSecant),
typeof (NonLinearEqFalsePos), typeof (NonLinearEqRidder),
typeof (NonLinearEqBrent), typeof (NonLinearEqNewton)};
/// <summary>
/// Хранит ссылку на текущий объект решения нелинейного уравнения.
/// Типом объекта является интерфейс INonLinearEquation
/// </summary>
INonLinearEquation nle;
/// <summary>
/// Хранит текущее состояние процесса
/// </summary>
TState state;
/// <summary>
/// Хранит текущее число итераций
/// </summary>
int iterations;
/// <summary>
/// Хранят текущие границы интервала изоляции и текущие погрешности
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
137
/// </summary>
double curLeftSide, curRightSide, curAbsAcc, curRelAcc;
Обратите внимание, что класс TState типа enum (перечислимый) описан
непосредственно в теле класса формы NonLinEqTest, а не вне его, как это требует,
например, синтаксис Delphi. В языке C# классы можно описывать внутри
других классов. Это уже встречалось в консольном приложении. Прочтите
комментарии.
Добавьте следующую часть кода класса формы NonLinEqTest, которая содержит
описание свойства State.
/// <summary>
/// Устанавливает состояние процесса
/// </summary>
TState State
{
set
{ // В состоянии ожидания компоненты, помещенные
// на GroupBoxIn активизируются (свойство Enabled).
// В состоянии счета - дезактивируются.
// В зависимости от состояния устанавливается текст в строке статуса
statusLabel.Text =
(groupBoxIn.Enabled = value == TState.waiting) ? "waiting" : "computing";
state = value;
}
}
Это внутреннее свойство класса (private), доступное только внутри класса.
Оно имеет только метод set. Значение соответствующего поля state можно
читать без обращения к свойству. Обратите внимание на синтаксис описания
свойства в C#, а также на синтаксис оператора, содержащего условное
выражение, внутри метода set.
Внесите внутрь уже имеющегося конструктора код, инициализирующий
некоторые поля класса. Окончательный вид конструктора
/// <summary>
/// Конструктор. Инициализирует поля и интерфейс.
/// </summary>
public NonLinEqTest ()
{
InitializeComponent ();
toolTip.SetToolTip (textBoxAbsAcc,
"Абсолютная погрешность >= " + NonLinearEquation.MinAbsAcc.ToString ("e8"));
toolTip.SetToolTip (textBoxRelAcc,
// Обратите внимание на метод Format класса String и на выражение
// форматируемой строки, где указаны знакоместа 0 и 1 вместе
// с требуемым форматом E (экспоненциальный).
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
138
// На эти места должны подставляться переменные из списка, следующего за строкой.
// В данном случае на место 0 должно подставляться значение константы MinRelAcc
// из класса NonLinearEquation, а на место 1 – значение максимально допустимой
// погрешности NonLinearEquation.MaxRelAcc.
String.Format ("Относительная погрешность должна быть в интервале [{0:E};{1:E}]",
NonLinearEquation.MinRelAcc, NonLinearEquation.MaxRelAcc));
// Свойство SelectedIndex равносильно ItemIndex в Delphi.
// При изменении этого свойства происходит событие,
// обработчик которого автоматически вызывается.
comboBoxMethod.SelectedIndex = 0;
// Соответствующий обработчик компоненты comboBoxFunction описан ниже.
// В нем инициализируются поля граничных значений интервала изоляции корня
// значениями по умолчанию.
comboBoxFunction.SelectedIndex = 0;
// Здесь фактически вызывается метод set свойства State.
State = TState.waiting;
// В окошки погрешностей подставляются значения погрешностей по умолчанию
textBoxAbsAcc.Text = NonLinearEquation.DefAbsAcc.ToString ();
textBoxRelAcc.Text = NonLinearEquation.DefRelAcc.ToString ();
// Текущие значения погрешностей инициализируются значениями по умолчанию.
curAbsAcc = NonLinearEquation.DefAbsAcc;
curRelAcc = NonLinearEquation.DefRelAcc;
}
Добавьте в описание класса следующие методы
/// <summary>
/// Вычисляет значение левой части уравнения
/// </summary>
/// <remarks>
/// Создает объект исключительной ситуации,
/// если аргумент оказывается вне области определения функции
/// </remarks>
/// <param name="x">
/// Аргумент
/// </param>
/// <returns>
/// Значение функции
/// </returns>
double f (double x)
{
switch ( comboBoxFunction.SelectedIndex )
{
case 0: return Math.Sin (x);
case 1:
if ( x > 1.0 || x == 0.0 )
throw new ArgumentOutOfRangeException
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
139
("Аргумент вышел из области определения!");
return Math.Tan (x) - Math.Sqrt (1.0 - x * x) / x;
default: return 0.0;
}
}
/// <summary>
/// Вычисляет производную левой части уравнения
/// </summary>
/// <remarks>
/// Создает объект исключительной ситуации,
/// если аргумент оказывается вне области определения функции
/// </remarks>
/// <param name="x">
/// Аргумент
/// </param>
/// <returns>
/// Значение функции
/// </returns>
double df (double x)
{
switch ( comboBoxFunction.SelectedIndex )
{
case 0: return Math.Cos (x);
case 1:
if ( x >= 1.0 || x == 0.0 )
throw new ArgumentOutOfRangeException
("Аргумент вышел из области определения!");
return 1.0 / Math.Cos (x) / Math.Cos (x) + 1.0 / Math.Sqrt (1.0 - x * x) / x / x;
default: return 0.0;
}
}
/// <summary>
/// Изменяет значения в окошках вывода результатов и задерживает подпроцесс
/// </summary>
/// <param name="sender">
/// Объект, вызывающий событие
/// </param>
/// <returns>
/// Логическое значение true или false для останова или, соответственно,
/// продолжения итерационного цикла
/// </returns>
bool onIteration (object sender)
{
textBoxIterations.Text = iterations++.ToString ();
textBoxRoot.Text = nle.Root.ToString ("e8");
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
140
textBoxRootFunc.Text = f (nle.Root).ToString ("e8");
// Обратите внимание на преобразование типа интерфейса в тип класса
// NonLinearEquation, необходимое из-за того, что у интерфейса нет свойства Delta.
textBoxDelta.Text = (nle as NonLinearEquation).Delta.ToString ("e8");
Application.DoEvents ();
Thread.Sleep (1000);
return false;
}
/// <summary>
/// Анализирует строку, введенную в окошко ввода
/// </summary>
/// <param name="sender">
/// Компонента типа TextBox, через которую осуществляется ввод
/// </param>
/// <param name="errorMsg">
/// Строка, содержащая сообщение об ошибке.
/// </param>
/// <returns>
/// Значение true, если набранная строка успешно обработана,
/// и false - в противном случае.
/// </returns>
// Обратите внимание на параметр с модификатором out.
// Этот параметр при вызове метода может иметь неопределенное значение.
// Он определяется только внутри метода и передается по ссылке.
bool Validate (TextBox sender, out string errorMsg)
{
// У класса String есть поле Empty - пустая строка
errorMsg = String.Empty;
double temp;
try
{ //Делается попытка прочесть строку как число
// В .net у класса Double есть для этого статический метод Parse
temp = Double.Parse (sender.Text);
}
catch //попытка неудачная
{
errorMsg = "Набранная строка не является вещественным числом!!" +
"(Возможно,поставлена точка,вместо десятичной запятой?!)";
return false;
}
// Специальные условия, диктуемые смыслом вводимой величины,
// должны соблюдаться для локальной переменной temp
if ( sender == textBoxLeft )
if ( temp >= curRightSide )
{
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
141
errorMsg = "Левая граница интервала изоляции должна быть меньше правой!!";
return false;
}
else
// Для преобразования строки в число есть класс Convert
// и его статический метод ToDouble
curLeftSide = Convert.ToDouble (textBoxLeft.Text);
if ( sender == textBoxRight )
if ( temp <= curLeftSide )
{
errorMsg = "Правая граница интервала изоляции должна быть больше левой!!";
return false;
}
else
curRightSide = Convert.ToDouble (textBoxRight.Text);
if ( sender == textBoxRelAcc )
if
( temp < NonLinearEquation.MinRelAcc || temp > NonLinearEquation.MaxRelAcc )
{
errorMsg = String.Format
("Относительная погрешность должна лежать в интервале [{0};{1}]!!",
NonLinearEquation.MinRelAcc, NonLinearEquation.MaxRelAcc);
return false;
}
else
curRelAcc = Convert.ToDouble (textBoxRelAcc.Text);
if ( sender == textBoxAbsAcc )
if ( temp < NonLinearEquation.MinAbsAcc )
{
errorMsg = String.Format
("Абсолютная погрешность должна быть больше {0}!!",
NonLinearEquation.MinAbsAcc);
return false;
}
else
curAbsAcc = Convert.ToDouble (textBoxAbsAcc.Text);
return true;
}
Обработчики формы
Теперь добавьте обработчики событий на компонентах формы.
 Войдите на строительную площадку формы и выделите компоненту
comboBoxFunction.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
142
 В окне Properties щелкните по кнопке с изображением молнии. Появится
список событий, которые обрабатывает компонента.
 Выберите в этом списке событие SelectedIndexChanged и дважды щелкните по
правому полю.
В коде появится скелет обработчика comboBoxFunction_SelectedIndexChanged.
 Внесите в него код заполняющий окошки ввода границ интервала изоляции
значениями по умолчанию. Так, что весь метод примет вид
private void comboBoxFunction_SelectedIndexChanged (object sender, EventArgs e)
{
textBoxLeft.Text =
(curLeftSide = defLeftSide [comboBoxFunction.SelectedIndex]).ToString ();
textBoxRight.Text =
(curRightSide = defRightSide [comboBoxFunction.SelectedIndex]).ToString ();
}
 Теперь добавьте обработчик события клика кнопки buttonCompute. Для этого
достаточно дважды щелкнуть по этой кнопке на строительной площадке.
 Добавьте в появившийся скелет метода код. Так, что весь метод примет вид
private void buttonCompute_Click (object sender, EventArgs e)
{
// Создается экземпляр класса, тип которого определяется как элемент массива
// типов nonLinearEqClasses с индексом, равным индексу выделенного элемента
// списка в компоненте comboBoxMethod
nle = (INonLinearEquation)Activator.CreateInstance (
nonLinearEqClasses [comboBoxMethod.SelectedIndex]);
// Ссылка на метод, возвращающий левую часть уравнения в объекте nle,
// устанавливается в значение f – ссылку на метод настоящей формы
nle.LeftHandSide = f;
// Событию OnBeforeIterations объекта nle добавляется обработчик, записанный
// здесь в явном виде.
nle.OnBeforeIterations += delegate
//Он содержит только два оператора – инициализацию счетчика итераций
// и установку состояния счета
{ iterations = 0; State = TState.computing; };
// Событию OnIteration объекта nle добавляется обработчик-метод настоящей
// формы onIteration
nle.OnIteration += onIteration;
// Событию OnAfterIterations объекта nle добавляется обработчик, записанный
// здесь в явном виде.
// Обработчик устанавливает свойство State в состояние ожидания
nle.OnAfterIterations += delegate { State = TState.waiting; };
try
{
// Устанавливаются поля погрешностей
nle.RelAcc = curRelAcc;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
143
nle.AbsAcc = curAbsAcc;
// Ссылка на метод, возвращающий производную левой части уравнения
// в объекте nle, устанавливается в значение df –
// ссылку на метод настоящей формы в том случае, если nle есть объект
// класса NonLinearEqNewton
if ( nle is NonLinearEqNewton )
(nle as NonLinearEqNewton).Derivative = df;
// В окошко результата (корня) вводится значение, возвращаемое методом
// GetRoot объекта nle. Обратите внимание на способ преобразования числа
// в строку
textBoxRoot.Text = nle.GetRoot (curLeftSide, curRightSide).ToString ();
}
catch ( Exception ex )
{
// При возникновении исключительной ситуации
// ее объект передается в качестве параметра ex в блок catch,
// где сообщение, переносимое объектом ex, выводится в окно сообщения.
// Окно формируется статическим методом Show
// библиотечного класса MessageBox.
// После этого происходит прерывание выполнения метода оператором return
MessageBox.Show (ex.Message); return;
}
// В окно вывода значения функции корня выводится это значение
textBoxRootFunc.Text = f (nle.Root).ToString ();
}
 Следующий обработчик события переноса фокуса (Enter) на компоненту
groupBoxIn. Выделите эту компоненту на форме и войдите в окно Properties.
 Щелкнув по кнопке с молнией, найдите событие Enter.
 Дважды щелкните по правому полю. Появится скелет обработчика
groupBoxIn_Enter. Заполните его кодом
private void groupBoxIn_Enter (object sender, EventArgs e)
{
// Обратите внимание на особый тип цикла, перечисляющего все
// элементы некоторого множества. В данном случае этим множеством являются
// все окошки, метки и компоненты типа ComboBox,
// принадлежащие группирующей компоненте groupBoxOut.
// В этом множестве условным оператором if ( cOut is TextBox )
// выделяются только окошки ввода типа TextBox.
// И эти окошки очищаются методом Clear.
foreach ( Control cOut in groupBoxOut.Controls )
if ( cOut is TextBox ) (cOut as TextBox).Clear ();
}
 Следующий обработчик предназначен для оценки верности величин,
вводимых через окошки ввода границ интервала изоляции и погрешностей.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
144
Событие, которое при этом следует обрабатывать, называется Validating. Для
его установки
o Выделите компоненту textBoxLeft на форме NonLinEqTest.
o Войдите в окно Properties и щелкните по кнопке с молнией.
o Найдите событие Validating и в правом поле наберите имя
обработчика textBoxIn_Validating. Нажмите Enter.
o В списке того же окна Properties (в верхней части окна) найдите
компоненту textBoxRight. Выделите его.
o Найдите в списке обработчиков то же событие Validating.
o На правом поле откройте список уже имеющихся обработчиков. В
списке должен быть только один, только что созданный обработчик
textBoxIn_Validating. Выберите его.
o В списке компонент найдите по очереди окошки ввода
погрешностей textBoxAbsAcc и textBoxRelAcc. К ним так же
присоедините обработчик textBoxIn_Validating.
В обработчике textBoxIn_Validating наберите код
private void textBoxIn_Validating (object sender, CancelEventArgs e)
{
string errorMsg;
// Вызов метода Validate, описанного выше
// Обратите внимание, что модификатор out пишется так же при вызове метода,
// если он указан при описании.
if ( !Validate (sender as TextBox, out errorMsg) )
{
// Если условия не выполняются, то ввод из окошка не происходит
e.Cancel = true;
// Текст в окошке выделяется методом Select для исправления
(sender as TextBox).Select (0, (sender as TextBox).Text.Length);
}
// Компонента показывается на экране, начинает мигать и
// при наведении возвращает сообщение, если строка errorMsg не пуста
errorProvider.SetError (sender as TextBox, errorMsg);
}
Так решается вопрос о проверке ввода в C#.
Теперь можно проверить проект на синтаксис и активизировать его. Перед
активизацией зайдите в окно Solution Explorer, выберите узел с именем проекта
wNonLinEqTest и, открыв на нем контекстное меню, выберите команду Set As
Startup Project. Имя узла должно быть выделено полужирным шрифтом. Теперь
командой Start Debugging из меню Debug можно активизировать проект и
поработать с ним, проверив его функции.
Полный текст кода модуля формы можно найти по ссылке.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
145
Вопросы для самоконтроля
1. Чем отличается динамически загружаемая библиотека от exe-файла?
2. Пространства имен и директива using в C#.
3. Что представляет собой тип классов delegate в C#?
4. Что такое сигнатура метода?
5. Что представляет собой тип класса интерфейс в C#?
6. Синтаксис описания событий, свойств и методов в интерфейсе.
7. Модификаторы доступа и члены класса в C#.
8. Что означает наследование классом интерфейса?
9. Синтаксис описания заголовка абстрактного класса в C#.
10.Каков синтаксис использования события в коде класса в C#?
11.Синтаксис описания параметров метода по ссылке в C#.
12.Синтаксис создания исключительной ситуации.
13.Синтаксис описания виртуальных и абстрактных методов в C#.
14.Синтаксис описания свойства класса в C#.
15.Какой класс содержит стандартные математические функции в
библиотеке .NET?
16.В каком классе библиотеки .NET находится постоянная NaN? Ее смысл.
17.Синтаксис вызова виртуального метода-предка в C#.
18.Смысл комментария с тройным слэшем (XML-комментарий) в C#.
19.Технология создания шаблона пустого решения в MS Visual Studio. Что
имеется в виду под термином «решение» (solution)?
20.Как добавляется шаблон библиотеки в MS Visual Studio?
21.Как создается консольное приложение в MS Visual Studio?
22.Поясните термин «статический метод». Сравните с методом класса в
Delphi.
23.Как «подшить» библиотеку к коду приложения в MS Visual Studio?
24.Поясните вложенное описание типов в C#.
25.Синтаксис описания массива типов классов в C#. Функция typeof и класс
Type.
26.Синтаксис описания и инициализации объектов в C#.
27.Что делает метод Activator.CreateInstance?
28.Синтаксис явного вызова делегата и передачи его событию в C#.
29.Синтаксис создания делегата и передачи его событию в C#.
30.Варианты сравнения типов классов в C#. Метод GetType().
31.Варианты преобразования типов в C#. Оператор as.
32.Формирование строки вывода в черное окно в C#.
33.Статические методы WriteLine, Write, ReadLine класса Console.
34.Escape-последовательность в C#.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
146
35.Что делает метод ToString класса Object?
36.Что делает метод Parse?
37.Как создается шаблон оконного приложения в MS Visual Studio?
38.Что означает модификатор static в описании класса в C#?
39.Смысл модификатора partial в описании классов в C#.
40.Что находится в файле Form1.Designer.cs?
41.Какой смысл в создании областей (regions) кода в текстовом редакторе?
42.Смысл свойства Text в контролах оконного проекта в MS Visual Studio.
43.Смысл компоненты ToolTip в библиотеке .NET и свойства ToolTip on
toolTip контролов.
44.Как поместить контрол в компоненту-контейнер StatusStrip? Для чего
используется эта компонента?
45.Компонента ErrorProvider и ее метод SetError.
46.Какой смысл модификатора readonly в C#?
47.Синтаксис форматирования строки в C#. Метод Format.
48.Синтаксис использования объектов перечислимого типа в C#.
49.Что делает статический метод DoEvents класса Application?
50.Что делает статический метод Sleep класса Thread?
51.Смысл модификатора out, сопровождающего параметр метода.
52.Преобразование строки с помощью методов класса Convert.
53.Какое событие наступает при изменении выбранного элемента списка в
компоненте ComboBox, и как обрабатывается это событие в данном коде?
54.Как обрабатывается возникновение исключительной ситуации в C#
(операторы try…catch)?
55.Что делает статический метод Show класса MessageBox?
56.Объясните синтаксис и смысл оператора цикла foreach и тело
обработчика groupBoxIn_Enter.
57.Поясните смысл второго параметра в обработчике события Validating. Как
этот параметр используется в коде обработчика?
58.Смысл метода Select объектов класса TextBox.
59.Как работает метод SetError объектов класса ErrorProvider в зависимости
от значений его параметров?
C++ CLR
Язык C++ в реализации фирмы Microsoft во многом схож с версией этого языка
фирмы Borland. Однако есть и отличия. Эти отличия объясняются, прежде
всего, тем, что базой версии C++ Borland служит библиотека классов VCL, а
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
147
базой версии C++ Microsoft платформа .NET. Аббревиатура CLR означает
Common Language Runtime – общий язык времени выполнения. Требования
CLR вносят определенные дополнения и изменения в синтаксис языка C++.
Здесь эти изменения специально отмечаются.
Библиотека классов
Как и в предыдущих разделах начните с построения модуля классов решения
нелинейных уравнений. Для чего
 Откройте среду MS Visual Studio.
 В меню File по команде New->Project… откройте окно New Project.
 На панели Project Types в узле Other Project Types выберите элемент Visual
Studio Solutions.
 Выделите элемент Blank Solution и наберите имя solCppNonLinEq в строке
Name. Нажмите Enter.
 В окне Solution Explorer щелкните правой кнопкой над именем
организованного только что решения solCppNonLinEq и выберите в
появившемся контекстном меню команду Add -> New Project…
 В появившемся окне Add New Project на панели слева Project Types:
выберите узел Other Languages -> Visual C++ -> CLR. На панели справа
должен появиться список шаблонов приложений. В нем выберите Class
Library. Дайте имя cppDllNonLinEq новой библиотеке.
 В результате среда должна создать ряд файлов, из которых нас, прежде
всего, будут интересовать два файла – файл заголовков («хэдер»)
cppDllNonLinEq.h и файл исходного кода («исходник», или cpp-файл)
cppDllNonLinEq.cpp.
Файл заголовков cppDllNonLinEq.h
По умолчанию среда откроет шаблон файла заголовков в виде
// cppDllNonLinEq.h
#pragma once
using namespace System;
namespace cppDllNonLinEq {
public ref class Class1
{
// TODO: Add your methods for this class here.
};
}
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
148
Здесь
 Вслед за первой строкой-комментарием с именем хэдера стоит директива
компилятору #pragma once, которая требует однократной компиляции файла
заголовков (сравните с директивами в C++ Borland, предназначенными для
той же цели). Компилятор не будет повторно открывать и читать этот файл
после первой директивы #include с его именем.
 Строка с директивой using в отличие от C# требует явного указания
служебного слова namespace перед именем пространства имен.
 Далее организуется пространство имен с именем cppDllNonLinEq по
умолчанию.
 Внутри дается скелет класса с именем Class1 по умолчанию. Обратите
внимание, в заголовке описания стоит сочетание ref class, а не просто
class. Таковы требования CLR, где есть два типа объектов – по ссылке (как
в данном случае ref) и по значению. В последнем случае префиксом должно
быть служебное слово value (value class, value struct и т.п.).
Сотрите шаблон объявленного класса Class1 и внесите в файл заголовка код
классов решения нелинейного уравнения. Так, что после этой редакции весь
файл заголовков примет вид
// cppDllNonLinEq.h
// В модуле реализованы 7 классов, обеспечивающих решение нелинейного уравнения f(x)=0
// на заданном интервале изоляции шестью различными методами.
// Исходным классом является абстрактный класс NonLinearEquation.
// Его 6 наследников реализуют конкретные алгоритмы решения уравнения:
// методом деления пополам (NonLinearEqBisection);
// методом секущей (NonLinearEqSecant);
// методом ложного положения (NonLinearEqFalsePos);
// методом Риддерса (NonLinearEqRidders);
// методом Брента (NonLinearEqBrent);
// и методом Ньютона-Рафсона (NonLinearEqNewton));
#pragma once
using namespace System;
namespace cppDllNonLinEq {
// Тип метода левой части уравнения f(x)=0
// Имя формального параметра можно не указывать
public delegate double TLeftHandSide(double);
//Тип события при каждой итерации сужения
// Знак ^ указывает ссылку на объект класса Object
// Класс Object является базовым в библиотеке .NET
public delegate bool TOnIteration(Object^);
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
149
// Тип события перед и после цикла решения
// Здесь имя формального параметра o указано явно, но это не обязательно
public delegate void EvHandler(Object^ o);
// Базовый абстрактный класс всех дальнейших классов, реализующих алгоритмы
// решения нелинейного уравнения f(x)=0.
// Класс TNonLinearEquation наследует от Object по умолчанию
public ref class NonLinearEquation abstract
{
public:
// Обратите внимание на синтаксис описания постоянных.
// Инициализировать при описании можно только статические поля в C++ CLR
static double const
MinRelAcc=1e-16,
//минимальная допустимая относительная погрешность
MaxRelAcc=0.01,
//максимальная допустимая относительная погрешность
DefRelAcc=MinRelAcc,
//относительная погрешность по умолчанию
MinAbsAcc=1e-15,
//минимальная допустимая абсолютная погрешность
DefAbsAcc=MinAbsAcc;
//абсолютная погрешность по умолчанию
// Члены класса, доступные только методам класса
// Обратите внимание на синтаксис модификаторов доступа
private:
// Хранит указатель на левую часть уравнения
TLeftHandSide^ leftHandSide;
// Хранит текущее значение корня
double root;
// Хранят значения аргумента и функции
// на левой и правой границах интервала изоляции
double leftSideArg,rightSideArg;
double leftSideFunc,rightSideFunc;
// Одним из условий завершения итерационного цикла является приближение
// к корню до некоторой минимальной
// по абсолютной величине неопределенности (абсолютная погрешность),
// либо неопределенности малой по отношению к значению самого корня
// (относительная погрешность)
// Хранит текущую неопределенность в значении корня (>0)
double delta;
// Хранят текущие погрешности
double relAcc,absAcc;
//Методы
// Реализует итерационный цикл и возвращает значение корня
double BasicLoop();
// Члены класса, доступные наследникам
protected:
// Метод Initialize предназначен для возможной инициализации полей,
// используемых наследниками.
// Хотя он не абстрактный, но пустой и виртуальный.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
150
// Вызывается внутри BasicLoop до входа в основной цикл и непосредственно
// перед наступлением события OnBeforeIterations.
virtual void Initialize();
// Абстрактный метод выполнения одной итерации приближения к корню.
// Метод DoIteration реализует конкретный алгоритм поиска корня
// на одном шаге итерации.
// В настоящем классе является абстрактным
// и должен быть перекрыт наследником.
// Своими двумя параметрами по ссылке метод DoIteration должен вернуть
// текущие значения корня CurRoot и его неопределенность CurDelta (>0).
// Эти параметры используются в условии завершения итерационного цикла.
// Кроме того, метод DoIteration возвращает
// собственное условие завершения итерационного цикла
// значениями true илм false.
// Обратите внимание на синтаксис описания параметров по ссылке (знак %)
// и синтаксис описания абстрактного виртуального метода в C++ CLR.
virtual bool DoIteration(double% CurRoot,double% CurDelta)abstract;
// Члены класса, доступные любому приложению
public:
// События
// Событие OnBeforeIteration наступает сразу после вызова метода Initialize.
// Обратите внимание на синтаксис, указывающий ссылку знаком ^.
event EvHandler^ OnBeforeIterations;
// Событие OnIteration наступает сразу после завершения каждой итерации,
// если метод DoIteration не прерывает итерационный цикл, возвратив true.
event TOnIteration^ OnIteration;
// Событие OnAfterIteration наступает сразу после
// завершения итерационного цикла.
event EvHandler^ OnAfterIterations;
// Свойства
// Возвращает и устанавливает текущее значение абсолютной погрешности
// Обратите внимание на синтаксис описания свойства в C++ CLR
property double AbsAcc
{
double get(){return absAcc;};
void set(double value)
{
absAcc= value<MinAbsAcc?MinAbsAcc:value;
};
};
// Возвращает текущую неопределенность корня
property double Delta {double get(){return delta;}};
// Возвращает левую часть уравнения
property TLeftHandSide^ LeftHandSide
{
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
151
TLeftHandSide^ get(){return leftHandSide;};
void set(TLeftHandSide^ value){leftHandSide=value;}
};
// Возвращает текущее значение аргумента в левой точке
property double LeftSideArg{double get(){return leftSideArg;}};
// Возвращает текущее значение функции в левой точке
property double LeftSideFunc{double get(){return leftSideFunc;}};
// Возвращает и устанавливает текущее значение относительной погрешности
property double RelAcc
{
double get(){return relAcc;};
void set(double value)
{
relAcc=
value<MinRelAcc?MinRelAcc:value>MaxRelAcc?MaxRelAcc:value;
}
};
// Возвращает текущее значение аргумента в правой точке
property double RightSideArg
{
double get(){return rightSideArg;}
}
// Возвращает текущее значение функции в правой точке
property double RightSideFunc{double get(){return rightSideFunc;}};
// Возвращает текущее значение корня
property double Root{double get(){return root;}};
// Инициализирует поля класса
NonLinearEquation()
{
AbsAcc=DefAbsAcc;
RelAcc=DefRelAcc;
}
// Определяет и возвращает корень уравнения root
// на интервале изоляции [aLeftSideArg;aRightSideArg]
virtual double GetRoot(double aLeftSideArg, double aRightSideArg);
};
// Класс NonLinearEqBisection - наследник NonLinearEquation
// Решает уравнение f(x)=0 методом деления отрезка пополам (Bisection)
public ref class NonLinearEqBisection: NonLinearEquation
{
private:
// Хранят текущие значения аргумента, функции и неопределенности корня
double curArg,curFunc,curDelta;
protected:
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
152
// Реализует алгоритм поиска корня методом деления пополам
// Обратите внимание на синтаксис описания виртуального метода в базе
// и перекрывающего виртуального метода в наследнике:
// используются модификаторы virtual и override одновременно
virtual bool DoIteration(double% CurRoot, double% CurDelta) override;
// Инициализирует специальные поля класса перед входом в итерационный цикл
virtual void Initialize() override;
};
//Класс TNonLinearEqSecant - наследник, решающий уравнение f(x)=0 методом секущих
public ref class NonLinearEqSecant: NonLinearEquation
{
private:
double curArg,curFunc,curDelta,
// Хранят текущие значения аргумента и функции последнего приближения
lastArg,lastFunc;
protected:
virtual bool DoIteration(double% CurRoot,double% CurDelta) override;
virtual void Initialize() override;
};
//Класс TNonLinearEqFalsePos - наследник,
// решающий уравнение методом ложного положения
public ref class NonLinearEqFalsePos: NonLinearEquation
{
private:
double curDelta,curArg,curFunc,
// Хранят текущие значения аргумента и функции,
// отвечающие границам с отрицательным
// и положительным значениями функции
negSideArg,posSideArg,
negSideFunc,posSideFunc,
// Хранит текущую разность значений аргумента на краях интервала
deltaArg;
protected:
virtual bool DoIteration(double% CurRoot, double% CurDelta) override;
virtual void Initialize() override;
};
//Класс TNonLinearEqRidders - наследник, решающий уравнение методом Ридерса
public ref class NonLinearEqRidders: NonLinearEquation
{
private:
double leftArg,rightArg,leftFunc,rightFunc,curArg,curFunc;
protected:
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
153
virtual bool DoIteration(double% CurRoot,double% CurDelta) override;
virtual void Initialize() override;
};
//Класс - наследник, решающий уравнение методом Брента
public ref class NonLinearEqBrent: NonLinearEquation
{
private:
double firstPointArg,firstPointFunc,
secondPointArg,secondPointFunc,
thirdPointArg,thirdPointFunc,
firstDelta,secondDelta,tempDelta;
protected:
virtual bool DoIteration(double% CurRoot,double% CurDelta) override;
virtual void Initialize() override;
};
//Класс - наследник, решающий уравнение методом Ньютона
public ref class NonLinearEqNewton: NonLinearEquation
{
private:
// Хранит текущую ссылку на метод, возвращающий производную функции,
// стоящей в левой части уравнения f(x) = 0.
TLeftHandSide^ derivative;
double lowArg,highArg,deltaArg,prevDeltaArg,curArg,curFunc,curDerivative;
protected:
virtual bool DoIteration(double% CurRoot,double% CurDelta) override;
virtual void Initialize() override;
public:
// Устанавливает и возвращает ссылку на производную
property TLeftHandSide^ Derivative
{
TLeftHandSide^ get(){return derivative;};
void set(TLeftHandSide^ value)
{derivative=value;}
};
virtual double GetRoot(double aLeftSideArg,double aRightSideArg) override;
};
}
Проанализируйте комментарии в тексте и сравните с версиями заголовков тех
же классов, написанных на C++ Borland, а так же с описанием в C#. Текст файла
заголовков библиотеки cppDllNonLinEq.h доступен по ссылке.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
154
Файл кода («исходник») cppDllNonLinEq.cpp
В окне Solution Explorer в узле Source Files выберите файл исходника
cppDllNonLinEq.cpp библиотеки. Среда дала пустой файл в виде
// This is the main DLL file.
#include "stdafx.h"
#include "cppDllNonLinEq.h"
Здесь есть только директивы, включающие в библиотеку файл заголовков
стандартной библиотеки stdafx.h и только что созданный файл заголовков
строящейся библиотеки cppDllNonLinEq.h.
Добавьте директиву включения файла заголовков еще одной библиотеки #include
"stddef.h" и директиву
using namespace System;
В этом месте добавляется код методов классов, описанных в файле заголовков.
// Методы абстрактного класса
// Реализует итерационный цикл. Возвращает корень уравнения.
// Обратите внимание на описание заголовка метода, где используется оператор
// ссылки :: для формирования расширенного имени метода
// с указанием имени пространства имен библиотеки и имени класса.
// Указание имени пространства имен библиотеки cppDllNonLinEq можно избежать,
// если в заголовке добавить директиву using namespace cppDllNonLinEq;
// Здесь это не сделано.
double cppDllNonLinEq::NonLinearEquation::BasicLoop()
{
Initialize();//Инициализация полей в наследниках
// Здесь формируется событие перед началом цикла итераций
// Обратите внимание на отсутствие проверки условия // есть обработчик, или его нет.
// Если у события нет обработчика, то этот оператор будет опущен.
OnBeforeIterations(this);
//Основной цикл
do {}
while (!(DoIteration(root, delta)//Вызов алгоритма итерации
||
// Текущий корень покинул интервал изоляции
root>rightSideArg || root<leftSideArg
||
// Вызов обработчика события OnIteration
OnIteration(this)
// Проверка условия прекращения цикла по допустимой погрешности
// Обратите внимание на синтаксис вызова метода Abs класса Math, описанного
// в пространстве имен System. Имя System можно здесь опустить, так как в
// начале файла есть директива using mamespace System.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
155
// После имени класса Math так же стоит оператор :: и далее имя метода.
// Такой синтаксис вызова метода используется
// только для статических методов класса.
|| delta<2.0*relAcc*System::Math::Abs(root)+0.5*absAcc));
// Вызов обработчика события после конца цикла итераций
OnAfterIterations(this);
if (root>rightSideArg || root<leftSideArg)
{
System::ApplicationException^ ae=
// Обратите внимание, что используется служебное слово gcnew вместо new
// Это связано с требованиями CLR - объект должен удаляться автоматически
// "сборщиком мусора" (garbage collector - откуда gc)
gcnew ApplicationException("Алгоритм покинул интервал изоляции!");
throw ae;
}
return root;
}
// Инициализирует некоторые поля наследников. В этом классе метод пустой.
// Обратите внимание, что в описании теал виртуального метода модификатор
// virtual в заголовке не упоминается.
void cppDllNonLinEq::NonLinearEquation::Initialize()
{
}
// Ищет решение уравнения на интервале изоляции [aLeftSideArg;aRightSideArg]
double cppDllNonLinEq::NonLinearEquation::GetRoot(
double aLeftSideArg,double aRightSideArg)
{
if (!leftHandSide)
{
System::ApplicationException^ ae=
gcnew ApplicationException("Уравнение не задано!");
throw ae;
}
if (aLeftSideArg>=aRightSideArg)
{
System::ApplicationException^ ae=
gcnew ApplicationException("Левая граница больше правой!");
throw ae;
}
leftSideArg=aLeftSideArg;
rightSideArg=aRightSideArg;
// Значения на границе
leftSideFunc=leftHandSide(leftSideArg);
if (0==leftSideFunc) return root=leftSideArg;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
156
rightSideFunc=leftHandSide(rightSideArg);
if (0==rightSideFunc) return root=rightSideArg;
// Проверка знаков на границе
if (leftSideFunc*rightSideFunc>0.0)
{
System::ApplicationException^ ae=
gcnew ApplicationException("Это не интервал изоляции!");
throw ae;
}
root=0.5*(rightSideArg+leftSideArg);
delta=rightSideArg-leftSideArg;
// Основной цикл
return root=BasicLoop();
}
// Реализация методов классов-наследников
// Класс, реализующий метод деления пополам
// Методы класса NonLinearEqBisection
// Реализует алгоритм деления интервала пополам
bool cppDllNonLinEq::NonLinearEqBisection::DoIteration
(double% CurRoot,double% CurDelta)
{
double tempArg;
curFunc=LeftHandSide(CurRoot=(tempArg=curArg+(curDelta*=0.5)));
CurDelta= Math::Abs(curDelta);
if (curFunc<=0.0) curArg=tempArg;
return 0.0==curFunc;
}
// Инициализирует некоторые поля класса
void cppDllNonLinEq::NonLinearEqBisection::Initialize()
{
if (LeftSideFunc>0.0)
{
curDelta=LeftSideArg-RightSideArg;
curArg=RightSideArg;
curFunc=RightSideFunc;
}else
{
curDelta=RightSideArg-LeftSideArg;
curArg=LeftSideArg;
curFunc=LeftSideFunc;
}
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
157
}
// Метод секущей
// Реализация методов класса NonLinearEqSecant
// Реализует алгоритм метода секущей
bool cppDllNonLinEq::NonLinearEqSecant::DoIteration
(double% CurRoot,double% CurDelta)
{
curDelta=(lastArg-curArg)*curFunc/(curFunc-lastFunc);
lastArg=curArg;
lastFunc=curFunc;
curFunc=LeftHandSide(CurRoot=curArg+=curDelta);
CurDelta=Math::Abs(curDelta);
return 0.0==curFunc;
}
// Инициализирует некоторые поля класса
void cppDllNonLinEq::NonLinearEqSecant::Initialize()
{
lastFunc=
Math::Abs(LeftSideFunc)<Math::Abs(RightSideFunc)?
(curArg=LeftSideArg,curFunc=LeftSideFunc,
lastArg=RightSideArg,RightSideFunc):
(curArg=RightSideArg,curFunc=RightSideFunc,
lastArg=LeftSideArg,LeftSideFunc);
}
// Метод ложного положения
// Реализация методов класса NonLinearEqFalsePos
// Реализует алгоритм метода ложного положения
bool cppDllNonLinEq::NonLinearEqFalsePos::DoIteration
(double% CurRoot,double% CurDelta)
{
curFunc=LeftHandSide(curArg=negSideArg+
deltaArg*negSideFunc/(negSideFunc-posSideFunc));
if (curFunc<0.0)
{
curDelta=negSideArg-curArg;
negSideArg=curArg;
negSideFunc=curFunc;
} else
{
curDelta=posSideArg-curArg;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
158
posSideArg=curArg;
posSideFunc=curFunc;
}
deltaArg=posSideArg-negSideArg;
CurRoot=curArg;CurDelta=Math::Abs(curDelta);
return 0.0==curFunc;
}
// Инициализирует некоторые поля класса
void cppDllNonLinEq::NonLinearEqFalsePos::Initialize()
{
posSideFunc=
LeftSideFunc<0.0?
(negSideArg=LeftSideArg,negSideFunc=LeftSideFunc,
posSideArg=RightSideArg,RightSideFunc):
(negSideArg=RightSideArg,negSideFunc=RightSideFunc,
posSideArg=LeftSideArg,LeftSideFunc);
deltaArg=posSideArg-negSideArg;
}
// Метод Ридерса
// Реализация методов класса NonLinearEqRidders
// Реализует алгоритм Ридерса
bool cppDllNonLinEq::NonLinearEqRidders::DoIteration
(double% CurRoot,double% CurDelta)
{
double midpointArgValue=0.5*(leftArg+rightArg);
double midpointFuncValue=LeftHandSide(midpointArgValue);
double temp=Math::Sqrt(midpointFuncValue*midpointFuncValue-leftFunc*rightFunc);
if (0.0==temp) return true;
double newArg=midpointArgValue+(midpointArgValue-leftArg)*
(leftFunc>=rightFunc?1.0:-1.0)*midpointFuncValue/temp;
if ((CurDelta=Math::Abs(curArg-newArg))<AbsAcc)
{
CurRoot=curArg;
return true;
}
curFunc=LeftHandSide(curArg=newArg);
if ((midpointFuncValue<0.0) ^ (curFunc<0.0))
{
leftArg=midpointArgValue;
leftFunc=midpointFuncValue;
rightArg=curArg;
rightFunc=curFunc;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
159
} else
if ((leftFunc<0.0) ^ (curFunc<0.0))
{
rightArg=curArg;
rightFunc=curFunc;
} else
if ((rightFunc<0.0) ^ (curFunc<0.0))
{
leftArg=curArg;
leftFunc=curFunc;
}
CurRoot=curArg;
CurDelta=Math::Abs(rightArg-leftArg);
return 0.0==curFunc;
}
// Инициализирует некоторые поля класса
void cppDllNonLinEq::NonLinearEqRidders::Initialize()
{
leftArg=LeftSideArg;rightArg=RightSideArg;
leftFunc=LeftSideFunc;rightFunc=RightSideFunc;
curArg=double::MaxValue;
}
// Метод Брента
// Реализация методов класса NonLinearEqBrent
// Реализует алгоритм Брента
bool cppDllNonLinEq::NonLinearEqBrent::DoIteration
(double% CurRoot, double% CurDelta)
{
double numerator,denominator,aFraction,bFraction,aMin,bMin,CurAcc;
if (secondPointFunc*thirdPointFunc>0.0)
{
thirdPointArg=firstPointArg;thirdPointFunc=firstPointFunc;
firstDelta=secondPointArg-firstPointArg;secondDelta=firstDelta;
}
if (Math::Abs(thirdPointFunc)<Math::Abs(secondPointFunc))
{
firstPointArg=secondPointArg;secondPointArg=thirdPointArg;thirdPointArg=firstPointArg;
firstPointFunc=secondPointFunc;secondPointFunc=thirdPointFunc;thirdPointFunc=firstPointFunc;
}
tempDelta=0.5*(thirdPointArg-secondPointArg);
CurAcc=2.0*RelAcc*Math::Abs(secondPointArg)+0.5*AbsAcc;
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
160
if ((CurDelta=Math::Abs(tempDelta))<CurAcc || 0.0==secondPointFunc)
{
CurRoot=secondPointArg;
return true;
}
if (Math::Abs(secondDelta)>=CurAcc && Math::Abs(firstPointFunc)>
Math::Abs(secondPointFunc))
{
bFraction=secondPointFunc/firstPointFunc;
if (firstPointArg==thirdPointArg)
{
numerator=2.0*tempDelta*bFraction;denominator=1.0-bFraction;
} else
{
denominator=firstPointFunc/thirdPointFunc;aFraction=secondPointFunc/thirdPointFunc;
numerator=bFraction*(2.0*tempDelta*denominator*(denominator-aFraction)
-(secondPointArg-firstPointArg)*(aFraction-1.0));
denominator=(denominator-1.0)*(aFraction-1.0)*(bFraction-1.0);
}
if (numerator>0.0) denominator=-denominator;
numerator=Math::Abs(numerator);
aMin=3.0*tempDelta*denominator-Math::Abs(CurAcc*denominator);
bMin=Math::Abs(secondDelta*denominator);
if (2.0*numerator<(aMin<bMin?aMin:bMin))
{
secondDelta=firstDelta;firstDelta=numerator/denominator;
} else
{
firstDelta=tempDelta;secondDelta=firstDelta;
}
} else
{
firstDelta=tempDelta;secondDelta=firstDelta;
}
firstPointArg=secondPointArg;firstPointFunc=secondPointFunc;
if (Math::Abs(firstDelta)>CurAcc) secondPointArg+=firstDelta;
else secondPointArg += Math::Sign(tempDelta)*CurAcc;
secondPointFunc=LeftHandSide(secondPointArg);
CurRoot=secondPointArg;
return 0.0==secondPointFunc;
}
// Инициализирует некоторые поля класса
void cppDllNonLinEq::NonLinearEqBrent::Initialize()
{
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
161
tempDelta=(thirdPointArg=secondPointArg=RightSideArg)-(firstPointArg=LeftSideArg);
firstPointFunc=LeftSideFunc;
thirdPointFunc=secondPointFunc=RightSideFunc;
}
// Метод Ньютона-Рафсона
// Реализация методов класса NonLinearEqNewton
// Реализует алгоритм Ньютона-Рафсона
bool cppDllNonLinEq::NonLinearEqNewton::DoIteration
(double% CurRoot, double% CurDelta)
{
double tempArg;
if (((curArg-highArg)*curDerivative-curFunc)*((curArg-lowArg)*curDerivative-curFunc)>0
||
Math::Abs(2.0*curFunc)>Math::Abs(prevDeltaArg*curDerivative))
{
prevDeltaArg=deltaArg;
deltaArg=0.5*(highArg-lowArg);
curArg=lowArg+deltaArg;
if (lowArg==curArg)
{
CurRoot=curArg;
CurDelta=Math::Abs(deltaArg);
return true;
}
}
else
{
prevDeltaArg=deltaArg;
deltaArg=curFunc/curDerivative;
tempArg=curArg;
curArg-=deltaArg;
if (curArg==tempArg)
{
CurRoot=curArg;
CurDelta=Math::Abs(deltaArg);
return true;
}
}
CurDelta=Math::Abs(deltaArg);
CurRoot=curArg;
if (CurDelta<2.0*RelAcc*Math::Abs(curArg)+0.5*AbsAcc) return true;
curFunc=LeftHandSide(curArg);
curDerivative=derivative(curArg);
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
162
if (curFunc<0.0) lowArg=curArg; else highArg=curArg;
return 0.0==curFunc;
}
// Инициализирует некоторые поля класса
void cppDllNonLinEq::NonLinearEqNewton::Initialize()
{
highArg=LeftSideFunc<0.0?(lowArg=LeftSideArg,RightSideArg):
(lowArg=RightSideArg,LeftSideArg);
curArg=Root;
deltaArg=prevDeltaArg=Delta;
curFunc=LeftHandSide(curArg);
curDerivative=derivative(curArg);
}
// В методе Ньютона-Рафсона проверяет наличие производной функции f(x)
double cppDllNonLinEq::NonLinearEqNewton::GetRoot(
double aLeftSideArg, double aRightSideArg)
{
if (!derivative)
{
System::ApplicationException^ ae=
gcnew ApplicationException("Производная не задана!");
throw ae;
}
// Обратите внимание на синтаксис вызова метода предка
return TNonLinearEquation::GetRoot(aLeftSideArg,aRightSideArg);
};
Обратите внимание на имеющиеся комментарии и отличия в языке C++ CLR от
C++ Borland и C#. Текст файла-исходника библиотеки cppDllNonLinEq.cpp можете
взять по ссылке.
Консольное приложение на C++ CLR
По аналогии с прежними консольными приложениями строится приложение,
тестирующее библиотеку классов решения нелинейного уравнения на C++ CLR.
 Войдите в окно Solution Explorer.
 Выберите узел решения solCppNonLinEq и правой кнопокой вызовите
контекстное меню.
 Командой Add -> New Project… откройте окно New Project/
 Выберите окно шаблонов для C++ CLR и на правой панели шаблон
консольного приложения.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
163
 Назовите его cnslCppNonLinEqTest. Среда откроет ряд файлов и покажет
редакционное окно с шаблоном консольного приложения из файла
cnslCppNonLinEqTest.cpp.
// cnslCppNonLinEqTest.cpp : main project file.
#include "stdafx.h"
using namespace System;
int main(array <System::String ^> ^args)
{
// Обратите внимание на символ L, предшествующий строке.
// Эта особенность C++ CLR указывает, что строка представлена в расширенном коде
// В расширенном коде каждому символу соответствует не один, а несколько байт, что
// позволяет использовать символы различных языков.
Console::WriteLine(L"Hello World");
return 0;
}
Это приложение, как принято в языках C, вводит функцию main. Функция main
возвращает код выхода из приложения типа int, а в качестве параметра имеет
ссылку на массив строк args – содержание командной строки (см. аналогичный
раздел C++ Borland).
Обратите внимание на синтаксис описания формального параметра функции
main. Так описываются массивы в C++ CLR.
Теперь
 Добавьте к ссылкам консольного приложения ссылку на созданную
библиотеку. Для этого в окне Solution Explorer на узле консольного
приложения cnslCppNonLinEqTest через контекстное меню выберите команду
References…
 На панели References открывшегося окна cnslCppNonLinEqTest Property Pages
нажмите кнопку Add New Reference…
 В открывшемся окне Add Reference выберите закладку Projects и на ней
выделите и добавьте библиотеку cppDllNonLinEq. Имя библиотеки должно
появиться в списке ссылок.
 В начало модуля консольного приложения cnslCppNonLinEqTest добавьте
строку
using namespace cppDllNonLinEq;.
Код консольного приложения
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
164
Добавьте к коду консольного приложения перед описанием функции main код
тестирующего класса, подобного тому, что использовался в консольных
приложениях предыдущих версий.
ref class nonLinEqTest
{
// По умолчанию члены класса имеют доступ private
// Хранят номер уравнения и число итераций
int eq, iterations;
// Массив классов, реализующих различные методы решения нелинейного уравнения
// В C++ CLR инициализировать можно только статические поля
// Обычные поля (поля объекта) можно инициализировать
// только внутри конструктора или другого нестатического метода.
// Это отличает C++ от C#
// Обратите внимание так же на синтаксис описания массива ссылок на типы классов
// Здесь Type – стандартный класс библиотеки .NET
static array<Type^> const ^ method = {NonLinearEqBisection::typeid,
NonLinearEqBrent::typeid, NonLinearEqFalsePos::typeid,
NonLinearEqNewton::typeid, NonLinearEqRidders::typeid,
NonLinearEqSecant::typeid};
// Инициализация последовательности случайных чисел
static Random^ rnd=gcnew Random();
// Возвращает левую часть уравнения
double f(double x)
{
switch (eq)
{
case 0: return Math::Sin(x);
case 1:
if (x>1.0 || x==0)
{
System::ApplicationException^ ae=
gcnew ApplicationException("Аргумент вне области определения!");
throw ae;
}
return Math::Tan(x)-Math::Sqrt(1.0-x*x)/x;
default: return 0.0;
}
}
// Возвращает производную левой части уравнения
double fder(double x)
{
switch (eq)
{
case 0: return Math::Cos(x);
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
165
case 1:
if (x>=1.0 || x==0)
{
System::ApplicationException^ ae=
gcnew ApplicationException("Аргумент вне области определения!");
throw ae;
}
return 1.0/Math::Cos(x)/Math::Cos(x)+1.0/Math::Sqrt(1.0-x*x)/x/x;
default: return 0.0;
}
}
// Обработчик перед началом итераций
void beforeIteration(Object^ sender)
{
iterations=0;
}
// Обработчик во время итераций
bool onIteration(Object^ sender)
{
iterations++;
return false;
}
// Обработчик после итераций
void afterIteration(Object^ sender)
{
Console::WriteLine("iterations = {0}",iterations);
}
public:
bool JustDoIt()
{
// Создается объект случайно выбранного класса
NonLinearEquation^ nle=
(NonLinearEquation^)Activator::CreateInstance(method[rnd->Next(6)]);
// Задается случайное уравнение
eq=rnd->Next(2);
// Событиям объекта передаются обработчики
// Обратите внимание на синтаксис вызова конструктора делегата.
// Задаются два аргумента - вызывающий объект и адрес (знак &) его метода
nle->OnBeforeIterations+=
gcnew EvHandler(this,&nonLinEqTest::beforeIteration);
nle->OnIteration+=gcnew TOnIteration(this,&nonLinEqTest::onIteration);
nle->OnAfterIterations+= gcnew EvHandler(this,&nonLinEqTest::afterIteration);
try
{
// Задается левая часть уравнения
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
166
nle->LeftHandSide=gcnew TLeftHandSide(this,&nonLinEqTest::f);
// Задается производная для метода Ньютона-Рафсона
if (nle->GetType()==NonLinearEqNewton::typeid)
((NonLinearEqNewton^)nle)->Derivative=
gcnew TLeftHandSide(this,&nonLinEqTest::fder);
// Печатается тип метода и уравнение
Console::WriteLine("\n"+nle->GetType()->ToString()+"\n"+
"Equation: "+(eq==0?"sin(x) = 0":"tn(x)-sqrt(1.0-x*x)/x = 0"));
// Вводятся границы интервала изоляции
Console::Write("Enter left side:");
double leftSide=Double::Parse(Console::ReadLine());
Console::Write("Enter right side:");
double rightSide=Double::Parse(Console::ReadLine());
try
{
// Вычисляется корень на заданном интервале
Console::WriteLine("root: {0}",nle->GetRoot(leftSide,rightSide));
}
catch (System::Exception ^e)
{
Console::WriteLine(e->Message);
}
}
catch (System::Exception ^e)
{
Console::WriteLine(e->Message);
}
Console::Write("Enter any key to continue; x - to end.");
return Console::ReadLine()!="x";
}
};
Внимательно ознакомьтесь с комментариями, приведенными в коде.
Код внутри функции main замените кодом, тестирующим классы решения
нелинейного уравнения. Так, что функция main примет вид
int main(array<System::String ^> ^args)
{
// Создается ссылка на экземпляр nlTest класса nonLinEqTest с вызовом конструктора
// по умолчанию
nonLinEqTest^ nlTest=gcnew nonLinEqTest();
// Цикл с постусловием вызывает метод JustDoIt объекта nlTest
// Обратите внимание на оператор ->, используемый в С++ для вызова метода
// объекта, заданного по ссылке.
do {}
while (nlTest->JustDoIt() );
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
167
return 0;
}
Текстовый вариант модуля консольного приложения cnslCppNonLinEqTest.cpp
возьмите по ссылке. Проверьте его на синтаксис и активизируйте, проведя
тестирование классов решения нелинейного уравнения. Перед активизацией
убедитесь, что имя консольного приложения в окне Solution Explorer выделено
полужирным шрифтом. Если это не так, через контекстное меню дайте команду
Set as StartUp Project (установить проект как стартующий).
Оконное приложение в C++ CLR
Добавьте к solCppNonLinEq в окне Solution Explorer новый проект командой
контекстного меню Add -> New Project…. Выберите в окне New Project на
закладке C++.CLR шаблон (template) оконного приложения Windows Forms
Application, дав новому проекту имя wCppNonLinEqTest.
Файлы оконного приложения
Среда создаст файлы нового оконного приложения и откроет файл заголовков
формы Form1.h [Design]с изображением строительной площадки формы.
Посмотрите в начале основной файл оконного приложения, содержащий
функцию main. Для этого через окно Solution Explorer откройте файл
wCppNonLinEqTest.cpp. Вот его содержание по умолчанию
// wCppNonLinEqTest.cpp : main project file.
#include "stdafx.h"
#include "Form1.h"
using namespace wCppNonLinEqTest;
[STAThreadAttribute]
int main(array<System::String ^> ^args)
{
// Enabling Windows XP visual effects before any controls are created
Application::EnableVisualStyles();
Application::SetCompatibleTextRenderingDefault(false);
// Create the main window and run it
Application::Run(gcnew Form1());
return 0;
}
Здесь практически ничего нового нет. Сравните с C++ Borland и C#. Закройте
это окно.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
168
Весь код будет набираться в окне файла заголовков формы Form1.h. В отличие от
C# в C++ CLR нет частичного (partial) описания классов. Чтобы увидеть код
класса формы нажмите F7, находясь в окне дизайнера, либо из контекстного
меню в окне Solution Explorer над узлом Form1.h выберите команду View Code.
Появится окно со следующим содержанием, подготовленным средой
#pragma once
namespace wCppNonLinEqTest {
using
using
using
using
using
using
namespace System;
namespace System::ComponentModel;
namespace System::Collections;
namespace System::Windows::Forms;
namespace System::Data;
namespace System::Drawing;
/// <summary>
/// Summary for Form1
///
/// WARNING: If you change the name of this class, you will need to change the
///
'Resource File Name' property for the managed resource compiler tool
///
associated with all .resx files this class depends on. Otherwise,
///
the designers will not be able to interact properly with localized
///
resources associated with this form.
/// </summary>
public ref class Form1 : public System::Windows::Forms::Form
{
public:
Form1(void)
{
InitializeComponent();
//
//TODO: Add the constructor code here
//
}
protected:
/// <summary>
/// Clean up any resources being used.
/// </summary>
~Form1()
{
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
169
if (components)
{
delete components;
}
}
private:
/// <summary>
/// Required designer variable.
/// </summary>
System::ComponentModel::Container ^components;
#pragma region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
void InitializeComponent(void)
{
this->components = gcnew System::ComponentModel::Container();
this->Size = System::Drawing::Size(300,300);
this->Text = L"Form1";
this->Padding = System::Windows::Forms::Padding(0);
this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
}
#pragma endregion
};
}
Сравните этот код с тем, что дает C# в файле дизайнера Form1.designer.cs.
Практически это то же самое, только набрано на языке C++ CLR. Постарайтесь
прочесть и понять комментарий, предложенный средой.
Дизайн окна приложения на C++ CLR
В дизайне окна на C++ CLR используются те же инструменты и те же
компоненты, что и в дизайне на C#.
Визуальные компоненты в среде MS Visual Studio находятся в окне Toolbox, а
окно свойств (Properties) открывается из меню View, как и все другие окна
среды.
 В начале измените имя файла формы. Это лучше сделать в окне Solution
Explorer. Щелкните правой кнопкой по имени файла Form1.h и в
контекстном меню выберите команду Rename. Введите новое имя
fNonLinEqTest. В файле проекта wCppNonLinEqTest.cpp соответственно
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
170
измените имя включаемого файла формы в директиве #include на #include
fNonLinEqTest.
 Теперь измените имя класса формы. Для этого войдите в редакционное
окно кода формы fNonLinEqTest.h и
o выбрав имя Form1, войдите в меню Edit и найдите команду Find and
Replace -> Quick Find
o Наберите в окошке Find what существующее имя класса формы
Form1, а в окошке Replace with новое имя NonLinEqTest. Нажмите
кнопку Replace All.
o Откройте файл проекта wCppNonLinEqTest.cpp и там так же проведите
замену Form1 на NonLinEqTest.
 Подключите к проекту wCppNonLinEqTest библиотеку классов CSdllNonLinEq,
построенную в предыдущем разделе.
Для этого
o Войдите в меню Project и выберите команду wCppNonLinEqTest
Properties….
o В открывшемся окне wCppNonLinEqTest Property Pages на левой панели
выберите узел Common Properties -> References (ссылки)
приложения wCppNonLinEqTest.
o Нажмите кнопку Add New Reference….
o В открывшемся окне Add Reference выберите закладку Projects, на
которой выберите библиотеку классов cppDllNonLinEq. Библиотека
должна оказаться в списке References приложения wCppNonLinEqTest.
o К списку using в начале файла формы fNonLinEqTest добавьте строку
using namesapace cppDllNonLinEq;
o Добавьте ссылку еще на одну библиотеку
using namespace System::Threading;
которая понадобится в коде.
После этого все классы с модификатором доступа public пространства
имен cppDllNonLinEq будут доступны оконному приложению.
Теперь перейдите к визуальному конструированию.
 В начале расширьте немного границы окна (потяните за края вправо и вниз).
По умолчанию оно слишком мало. Поместите на него из окна Toolbox
компоненту класса GroupBox из раздела Containers.
 В окне Properties измените его имя (свойство Name) на groupBoxIn, а значение
свойства Text на In.
 Перенесите на форму из Toolbox еще один объект класса GroupBox. Назовите
новый контрол groupBoxOut, а его свойству Text дайте значение Out.
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
171
 Наполните GroupBoxIn компонентами из окна Toolbox, которые понадобятся
для ввода информации в приложение:
o Поместите Label из раздела Common Controls в верхнюю левую
часть groupBoxIn. Назовите ее labelMethod, а свойству Text дайте
значение Method.
o Поместите вторую метку типа Label в верхнюю правую часть
groupBoxIn. Назовите labelFunc, а свойству Text дайте значение
Функция.
o Под левой меткой Method поместите компоненту класса ComboBox из
раздела Common Controls окна Toolbox. Назовите comboBoxMethod. В
нем будет располагаться список методов решения нелинейного
уравнения. Найдите свойство Items в окне Properties и щелкните
справа по кнопке с тремя точками. Должно открыться окно String
Collection Editor, в поле которого наберите список методов решения
уравнения следующего содержания
Bisection
Secant
False Position
Ridders'
Brent's
Newton's
o Этот список должен появляться при щелчке по правой кнопке
контрола comboBoxMethod. Свойство DropDownStyle в окне Properties
измените на DropDownList.
o Еще один объект класса ComboBox поместите симметрично под
меткой Функция. Назовите его comboBoxFunction. В нем будет список
функций нелинейных уравнений. Внесите в свойство Items список
sinx
tg(x)-x/sqrt(1-x^2)
o Измените свойство DropDownStyle на DropDownList.
o В разделе Common Controls окна Toolbox выберите компоненту
TextBox. Перенесите TextBox на форму в groupBoxIn и поместите ниже
контрола groupBoxMethod. Назовите textBoxLeft. В ней будет
помещаться число – левая граница интервала изоляции корня.
o В разделе Common Controls окна Toolbox выберите компоненту
ToolTip и перенесите ее на форму. Эта компонента будет носителем
текстов, которые появляются при наведении курсора мышки на
компоненты интерфейса окна. Назовите компоненту toolTip.
o Теперь у компоненты textBoxLeft в окне Properties появилось
свойство ToolTip on toolTip. Дайте ему значение в виде строки Левая
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
172
граница должна быть меньше правой!. Окошко с этой надписью будет
появляться на экране, когда пользователь наведет указатель мышки
на контрол textBoxLeft.
o Поместите слева от редакционного окошка textBoxLeft метку типа
Label. Дайте ей имя labelLeft и свойству Text дайте значение LeftSide=.
o Такую же компоненту типа TextBox поместите чуть правее, на одной
высоте с предыдущим окошком. Назовите ее textBoxRight. Через это
окошко будет вводиться правая граница интервала изоляции корня.
o Свойству ToolTip on toolTip дайте значение Правая граница должна быть
больше левой!.
o Аналогично предыдущему контролу поместите слева метку типа
Label. Назовите ее labelRight. Свойству Text метки дайте значение
RightSide=.
o В левой части groupBoxIn чуть ниже textBoxLeft поместите новую
компоненту типа GroupBox. В этой компоненте будут расположены
редакционные окошки, в которых будут задаваться погрешности
решения нелинейного уравнения. Поэтому назовите ее groupBoxAcc, а
свойству Text дайте значение Погрешности.
o Внутрь groupBoxAcc поместите два редакционных окошка типа
TextBox. Назовите их textBoxAbsAcc и textBoxRelAcc соответственно.
Через эти окошки будут вводиться абсолютная и относительная
погрешности.
o Над ними поместите по одной метке с именами labelAbsAcc и
labelRelAcc соответственно. Свойствам Text меток дайте значения
Абсолютная и Относительная.
o Внутрь groupBoxIn, но вне groupBoxAcc, справа от последнего
поместите кнопку (объект типа Button). Назовите ее buttonCompute, а
свойству Text дайте значение Считай. Эта кнопка будет инициировать
процесс счета.
Теперь необходимо заселить groupBoxOut.
 Поместите на groupBoxOut четыре компоненты типа TextBox в два столбца по
паре и назовите их textBoxRoot, textBoxRootFunc, textBoxDelta и textBoxIterations
соответственно.
 Свойствам ReadOnly этих компонент придайте значение true. В эти
редакционные окошки программа будет выводить результаты счета и их
редактирование не имеет смысла.
 Снабдите каждое из этих 4-ех окошек метками типа Label, назвав их
соответственно labelRoot, labelRootFunc, labelDelta и labelIterations. Свойства Text
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
173
этих
меток
установите соответственно в Корень, Функция в корне,
Неопределенность, Число итераций.
В заключение
 поместите на форму компоненту StatusStrip из раздела Menus & ToolBars. В
ней будет сообщаться о текущем состоянии программы. Дайте ей имя
statusStrip. Эта компонента является контейнером для других компонент.
Поместите в этот контейнер метку. Для этого
o В свойствах компоненты statusStrip найдите Items. Щелкните по
кнопке с многоточием. Это откроет окно Items Collection Editor.
o В нем выберите (в списке слева вверху) компоненту типа StatusLabel
и добавьте ее к statusStrip, нажав кнопку Add.
o На правой панели откроется редактор свойств новой компоненты.
Имя (Name) измените на statusLabel, а значение свойства Text сотрите.
Это свойство будет меняться динамически при работе программы.
 Поместите компоненту ErrorProvider из раздела Components. Назовите ее
errorProvider. Эта компонента поможет при анализе ввода строки из окошек
ввода, реагируя на неверный ввод числа визуальным образом. Объекты
класса ErrorProvider имеют метод SetError, устанавливающий содержание
строки сообщения об ошибке для заданной компоненты. Если эта строка не
пустая, то вызов метода SetError приводит к появлению на экране рядом с
компонентой
небольшого
мигающего
значка
с
изображением
восклицательного знака. При наведении курсора на этот значок появляется
строка сообщения об ошибке, переданная методу SetError.
Код формы
Теперь наберите код членов класса формы, который будет определять ее
поведение.
В начале добавьте в описание класса необходимые поля, аналогичные тем, что
использовались в предыдущих проектах.
// Обратите внимание на синтаксис описания перечислимого типа в C++ CLR
enum class TState { waiting, computing };
// Хранят левую и правую границу интервала изоляции по умолчанию
// для двух уравнений.
// В C++ CLR инициализировать в описании можно только статические поля
static array<double> ^defLeftSide = { -2.5, 0.1 },
^defRightSide = { 3.0, 0.9 };
// Хранит ссылки на VMT классов решения нелинейного уравнения.
static array<Type^> ^method = {NonLinearEqBisection::typeid,
NonLinearEqBrent::typeid, NonLinearEqFalsePos::typeid,
NonLinearEqNewton::typeid, NonLinearEqRidders::typeid,
NonLinearEqSecant::typeid};
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
174
// Хранит ссылку на текущий объект решения нелинейного уравнения.
NonLinearEquation^ nle;
// Хранит текущее состояние процесса
TState state;
// Хранит текущее число итераций
int iterations;
// Хранят текущие погрешности
double curLeftSide, curRightSide, curAbsAcc, curRelAcc;
Теперь добавьте описание свойства State.
// Устанавливает состояние процесса
property TState State
{
void set(TState value)
{ // В состоянии ожидания компоненты, помещенные
// на GroupBoxIn активизируются (свойство Enabled).
// В состоянии счета - дезактивируются.
// В заивисимости от состояния устанавливается текст в строке статуса
// Обратите внимание на синтаксис обращения к элементам перечислимого типа (::)
groupBoxIn->Enabled = value == TState::waiting;
statusLabel->Text =
value == TState::waiting ? "waiting" : "computing";
state = value;
}
}
Внесите внутрь уже имеющегося конструктора код, инициализирующий
некоторые поля класса. Окончательный вид конструктора
NonLinEqTest(void)
{
InitializeComponent();
toolTip->SetToolTip (textBoxAbsAcc,
// Обратите внимание на синтаксис вызова статических членов класса (::)
"Абсолютная погрешность >= " +
NonLinearEquation::MinAbsAcc.ToString ("e8"));
toolTip->SetToolTip (textBoxRelAcc,
String::Format ("Относительная погрешность должна быть в интервале [{0:E};{1:E}]",
NonLinearEquation::MinRelAcc, NonLinearEquation::MaxRelAcc));
comboBoxMethod->SelectedIndex = 0;
comboBoxFunction->SelectedIndex = 0;
// Здесь фактически вызывается метод set свойства State.
State = TState::waiting;
// В окошки погрешностей подставляются значения погрешностей по умолчанию
// Текущие значения погрешностей инициализируются значениями по умолчанию.
textBoxAbsAcc->Text = (curAbsAcc = NonLinearEquation::DefAbsAcc).ToString ();
textBoxRelAcc->Text = (curRelAcc = NonLinearEquation::DefRelAcc).ToString ();
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
175
}
Обратите внимание на различия вызова статического метода класса (оператор
::) и метода экземпляра, вызываемого ссылкой на экземпляр класса (оператор >).
Можно проверить работу приложения. В Solution Explorer выделите узел
wCppNonLinEqTest и в контекстном меню выберите команду Set as StartUp Project.
Имя узла wCppNonLinEqTest должно быть отображено в полужирном шрифте.
После этого командой Start Debugging из главного меню Debug среды (зеленая
стрелка) активизируйте приложение. Должно появиться изображение окна
примерно в такой форме
Код методов и обработчиков приложения
Приложение в этом виде, конечно, еще не готово. После описания свойства State
добавьте методы, подобные тем, что готовились в C#. Все эти методы должны
иметь доступ private, как описанные выше поля и свойство State. Конструктор
должен иметь доступ public.
// Возвращает значения левой части уравнения
double f (double x)
{
switch ( comboBoxFunction->SelectedIndex )
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
176
{
case 0: return Math::Sin (x);
case 1:
if ( x > 1.0 || x == 0.0 )
throw gcnew ArgumentOutOfRangeException
("Аргумент вышел из области определения!");
return Math::Tan (x) - Math::Sqrt (1.0 - x * x) / x;
default: return 0.0;
}
}
// Вычисляет производную левой части уравнения
double df (double x)
{
switch ( comboBoxFunction->SelectedIndex )
{
case 0: return Math::Cos (x);
case 1:
if ( x >= 1.0 || x == 0.0 )
throw gcnew ArgumentOutOfRangeException
("Аргумент вышел из области определения!");
return 1.0 / Math::Cos (x) / Math::Cos (x) + 1.0 / Math::Sqrt (1.0 - x * x) / x / x;
default: return 0.0;
}
}
// Инициализирует счетчик итераций и переводит процесс в состояние счета
void onBeforeIterations (Object^ sender)
{
iterations = 0; State = TState::computing;
}
// Изменяет значения в окошках вывода результатов и задерживает подпроцесс
bool onIteration (Object^ sender)
{
textBoxIterations->Text = iterations++.ToString ();
textBoxRoot->Text = nle->Root.ToString ("e8");
textBoxRootFunc->Text = f (nle->Root).ToString ("e8");
textBoxDelta->Text = nle->Delta.ToString ("e8");
Application::DoEvents ();
Thread::Sleep (1000);
return false;
}
// Возвращает процесс в состояние ожидания
void onAfterIterations (Object^ sender)
{
State = TState::waiting;
}
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
177
// Анализирует строку, введенную в окошко ввода
// Обратите внимание на описание параметра «по ссылке» errorMsg
// Знак & указывает, что передается адрес строки, а не сама строка при вызове метода
// Поэтому изменения в значении errorMsg сказываются на значении этого параметра
// после вызова метода.
// Это используется в обработчике события Validating, описанного ниже
bool Validate (TextBox^ sender, String^ &errorMsg)
{
// У класса String есть поле Empty - пустая строка
errorMsg = String::Empty;
double temp;
try
{ //Делается попытка прочесть строку как число
// В .net у класса Double есть для этого статический метод Parse
temp = Double::Parse (sender->Text);
}
catch(...) //попытка неудачная
{
errorMsg = "Набранная строка не является вещественным числом!!" +
"(Возможно,поставлена точка,вместо десятичной запятой?!)";
return false;
}
// Специальные условия, диктуемые смыслом вводимой величины,
// должны соблюдаться для локальной переменной temp
if ( sender == textBoxLeft )
if ( temp >= curRightSide )
{
errorMsg = "Левая граница интервала изоляции должна быть меньше правой!!";
return false;
}
else
// Для преобразования строки в число есть класс Convert
// и его статический метод ToDouble
curLeftSide = Convert::ToDouble (textBoxLeft->Text);
if ( sender == textBoxRight )
if ( temp <= curLeftSide )
{
errorMsg = "Правая граница интервала изоляции должна быть больше левой!!";
return false;
}
else
curRightSide = Convert::ToDouble (textBoxRight->Text);
if ( sender == textBoxRelAcc )
if
( temp < NonLinearEquation::MinRelAcc ||
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
178
temp > NonLinearEquation::MaxRelAcc )
{
errorMsg = String::Format
("Относительная погрешность должна лежать в интервале [{0};{1}]!!",
NonLinearEquation::MinRelAcc, NonLinearEquation::MaxRelAcc);
return false;
}
else
curRelAcc = Convert::ToDouble (textBoxRelAcc->Text);
if ( sender == textBoxAbsAcc )
if ( temp < NonLinearEquation::MinAbsAcc )
{
errorMsg = String::Format
("Абсолютная погрешность должна быть больше {0}!!",
NonLinearEquation::MinAbsAcc);
return false;
}
else
curAbsAcc = Convert::ToDouble (textBoxAbsAcc->Text);
return true;
}
Обратите внимания на отличия в коде по сравнению с C#.
Поставьте обработчик события выбора элемента SelectedIndexChanged списка
уравнений, которым управляет компонента comboBoxFunction. Для этого
 Выделите компоненту comboBoxFunction на форме
 Откройте окно Properties
 Перейдите на страницу событий (кнопка с изображением молнии)
 Выберите событие SelectedIndexChanged и щелкните дважда по правому полю
Наберите внутри обработчика код, устанавливающий текущие значения
интервала изоляции.
private:
System::Void comboBoxFunction_SelectedIndexChanged
(System::Object^ sender, System::EventArgs^ e)
{
textBoxLeft->Text =
(curLeftSide = defLeftSide [comboBoxFunction->SelectedIndex]).ToString ();
textBoxRight->Text =
(curRightSide = defRightSide [comboBoxFunction->SelectedIndex]).ToString ();
}
Поставьте обработчик клика по кнопке buttonCompute, дважды щелкнув по этой
кнопке на форме. Содержимое должно иметь вид
private: System::Void buttonCompute_Click (System::Object^ sender, System::EventArgs^ e)
{
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
179
nle = (NonLinearEquation^)Activator::CreateInstance (
method [comboBoxMethod->SelectedIndex]);
nle->LeftHandSide = gcnew TLeftHandSide(this, &NonLinEqTest::f);
nle->OnBeforeIterations += gcnew EvHandler(this, &NonLinEqTest::onBeforeIterations);
nle->OnIteration += gcnew TOnIteration(this, &NonLinEqTest::onIteration);
nle->OnAfterIterations += gcnew EvHandler(this, &NonLinEqTest::onAfterIterations);
try
{
nle->RelAcc = curRelAcc;
nle->AbsAcc = curAbsAcc;
if ( nle->GetType()==NonLinearEqNewton::typeid )
((NonLinearEqNewton^)nle)->Derivative = gcnew TleftHandSide (this,&NonLinEqTest::df);
textBoxRoot->Text = nle->GetRoot (curLeftSide, curRightSide).ToString ();
}
catch ( Exception^ ex )
{
MessageBox::Show (ex->Message); return;
}
textBoxRootFunc->Text = f(nle->Root).ToString ();
}
Здесь наиболее существенные отличия в вызове делегатов событий. Синтаксис
довольно сложен по сравнению с C#.
Добавьте обработчик входа (Enter) в компоненту groupBoxIn.
private: System::Void groupBoxIn_Enter(System::Object^ sender, System::EventArgs^ e)
{
// Очищаются окошки вывода результатов, принадлежащие groupBoxOut
for ( int i=0; i< groupBoxOut->Controls->Count; i++ )
if ( groupBoxOut->Controls[i]->GetType()== TextBox::typeid )
((TextBox^)groupBoxOut->Controls[i])->Clear ();
}
Обратите внимание на особенности синтаксиса, используемые в операторах
этого обработчика. Цикл проходит по всем контролам, принадлежащим
groupBoxOut. Из них выбираются только те, которые имеют тип TextBox, и эти
последние очищаются от текста.
Поставьте на окошки ввода границ интервала изоляции и погрешностей
обработчик события Validating с именем textBoxIn_Validating и наберите в нем код,
проверяющий правильность ввода в эти окошки.
private:
System::Void textBoxIn_Validating
(System::Object^ sender, System::ComponentModel::CancelEventArgs^ e)
{
String^ errorMsg;
// Здесь строка errorMsg принимает значение, вырабатываемое методом Validate
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
180
if ( !Validate ((TextBox^)sender, errorMsg) )
{
// Если условия не выполняются, то ввод из окошка не происходит
e->Cancel = true;
// Текст в окошке выделяется для исправления
((TextBox^)sender)->Select (0, ((TextBox^)sender)->Text->Length);
}
// Компонента показывается на экране, начинает мигать и
// при наведении возвращает сообщение, если строка errorMsg не пуста
errorProvider->SetError ((TextBox^)sender, errorMsg);
}
Просмотрите синтаксис и смысл действий. Активизируйте проект и посмотрите
результат. Поработайте с приложением, проверяя работу различных классов
решения нелинейного уравнения. Полный текст кода формы можно взять по
ссылке.
Вопросы для самоконтроля
1. Особенность директивы using в C++ CLR.
2. Что делает директива #pragma once?
3. Какой смысл модификаторов ref и value в заголовке описания класса в
C++ CLR?
4. Какой знак определяет ссылку на объект данного класса в C++ CLR?
5. Правила инициализации полей в C++ CLR.
6. Синтаксис описания параметров по ссылке в C++ CLR.
7. Синтаксис описания свойств в C++ CLR.
8. Синтаксис описания виртуальных методов и, методов, перекрывающих
виртуальные у наследников.
9. Особенность синтаксиса вызова события в C++ CLR.
10.Особенность синтаксиса создания экземпляра объекта в C++ CLR.
11.Синтаксис функции main в С++ CLR. Параметр-массив строк.
12.Смысл префикса L при формировании строки в C++ CLR.
13.Синтаксис описания массива ссылок на типы классов.
14.Синтаксис вызова конструктора делегата для подключения обработчика к
событию в C++ CLR.
15.Особенность среды C++ CLR при изменении имени формы.
16.Как установить ссылку на библиотеку в C++ CLR?
17.Синтаксис описания класса перечислимого типа в C++ CLR.
18.Различия в синтаксисе вызова статического метода класса и метода
экземпляра в C++ CLR.
19.Синтаксис описания параметров ссылочного типа по ссылке (знак &).
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
181
Программирование в современных средах. Фомин Г.В. 2009. ЮФУ
Download