Сравнительное объектно-ориентированное проектирование в

advertisement
Министерство образования и науки Российской Федерации
Федеральное агентство по образованию
Государственное образовательное учреждение
высшего профессионального образования
«РОСТОВСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ»
МЕТОДИЧЕСКИЕ УКАЗАНИЯ
к курсу программирования
для студентов физического факультета
Сравнительное объектно-ориентированное
проектирование
Delphi vs C++ vs C#
Часть 1
Ростов-на-Дону
2005
Методические указания разработаны кандидатом физико-математических
наук, доцентом кафедры теоретической и вычислительной физики Г.В.
Фоминым.
Ответственный редактор
доктор. физ.-мат. наук, профессор В.П. Саченко
Компьютерный набор и верстка
Г.В. Фомин
Печатается в соответствии с решением кафедры теоретической и
вычислительной физики физического факультета РГУ, протокол №28 от 21
июня 2005 г.
2
Сравнительное объектно-ориентированное
проектирование
Delphi vs C++ vs C#
Часть 1
Объектно-ориентированное проектирование представляет собой процесс
алгоритмизации того или иного вычислительного процесса посредством
написания специальных типов переменных – классов. Классы являются
основными носителями кода, который используется приложением или
программой. Приложение создает объекты – переменные описанных типовклассов.
При изучении технологии объектно-ориентированного проектирования важно
сравнить, каким образом общие понятия и технологии программирования
классов и событий реализованы в разных, наиболее популярных в настоящее
время средах программирования. Речь пойдет о трех языках программирования
Obect Pascal, C++ и C#, реализованных в двух средах фирмы Borland - Delphi
(до 7-ой версии включительно) и C++ Builder, объединенных библиотекой VCL
(Visual Component Library), и в среде Visual C# (начиная со 2-ой версии) фирмы
Microsoft, построенной на платформе .NET.
В книге приводятся прокомментированные коды нескольких классов,
действующих одинаково, но написанных в этих трех средах. Читателю
предлагается самостоятельно создать небольшие оконные приложения в
каждой из сред, тестирующих работу этих классов.
Класс численного решения трансцендентного уравнения f(x) = 0
Существует множество алгоритмов, сужающих интервал изоляции корня
трансцендентного уравнения. Здесь приводятся коды классов, реализующих
метод деления отрезка пополам и метод секущей (метод Ньютона). Базовым
классом является абстрактный класс TranscEquation, не реализующий
никакой конкретный метод сужения, но реализующий цикл сужения и
обеспечивающий потомкам основную часть необходимых свойств.
Delphi
В языке Object Pascal код класса (или нескольких классов, близких по смыслу)
принято писать в отдельном физическом файле – модуле unit с говорящим
именем. Модуль паскаля имеет стандартную для этого языка структуру –
интерфейс, видимый всем другим модулям, использующим данный, и раздел
реализации (implementation), доступный лишь внутри данного модуля. В
интерфейсе код не пишется. Там перечисляются лишь те типы, переменные и
заголовки процедур и функций, которые доступны внешним модулям.
Рассмотрим в начале интерфейсную секцию.
3
unit uTranscEquation;
{Модуль Classes является частью VCL (Visual Component Library)
В нем описан тип обработчика TNotifyEvent, используемый в
интерфейсе данного модуля.}
interface uses Classes;
type
{Завершение сужения интервала изоляции происходит либо
по условию точного равенства нулю интервала изоляции,
либо его сужения до величины, в Accuracy раз меньшей текущего
значения аргумента.
Для этого в описанных ниже классах используется
свойство EndFlag перечислимого типа TEndFlag.}
TEndFlag=(efZero,efAcc);
const
DefEndFlag:TEndFlag=efAcc;//Значение EndFlag по умолчанию
MinAccuracy=1e-18;
//Нижняя граница погрешности
MaxAccuracy=0.01;
//Верхняя граница погрешности
DefAccuracy=MinAccuracy; //Погрешность по умолчанию
type
//Обобщенный тип классов – наследников TTranscEquation
TTranscEqClass=class of TTranscEquation;
//Тип метода, возвращающего функцию левой части уравнения
TLeftPart=function(x:double):double of object;
{Тип обработчика, работающего на каждом шаге сужения интервала.
Параметр sender типа TObject часто является одним
из параметров обработчика. Он несет в себе ссылку на объект,
вызывающий обработчик и тем самым на все его свойства.}
TOnStep=function(sender:TObject):Boolean of object;
{Метод инициализации возвращает переменную перечислимого
типа TWhere,указывающую где находится корень уравнения.}
TWhere=(Into,Min,Max);
{абстрактный класс TTranscEquation решения трансцендентного
Уравнения не определяет метод сужения интервала OneStep.
Однако он описывает все основные поля,
свойства и вспомогательные методы, общие для
классов-потомков, реализующих метод OneStep.}
TTranscEquation=class //По умолчанию предком является TObject
//Члены класса, описанные с модификатором доступа private,
//не доступны объектам вне данного модуля
private
//Хранит функцию - левую часть уравнения
FLeftPart:TLeftPart;
//Хранит текущую погрешность
FAccuracy:double;
//Хранит текущее условие окончания сужения интервала
FEndFlag:TEndFlag;
//Хранит текущие обработчики событий
FOnStart:Classes.TNotifyEvent;//Перед входом в цикл
FOnStep:TOnStep;
//На каждом шаге цикла
{Возвращает угол наклона прямой,приближающей функцию
на текущем шаге сужения интервала изоляции.}
function GetDerivative:double;
//Возвращает значение функции в текущем приближении
function GetFunctionValue:double;
4
//Метод, устанавливающий текущую погрешность
//(используется в свойстве Accuracy)
procedure SetAccuracy(const value:double);
//Метод, реализующий основной цикл сужения интервала изоляции
function BasicLoop:double;
//Члены класса, описанные с модификатором доступа protected,
//доступны только классам-наследникам данного класса
protected
//Поля, хранящие текущие значения функции, аргумента и интервала
FCurrF,FCurrX,FCurrStep:double;
//Поле, хранящее текущее значение знака функции
FCurrSign:Shortint;
{Абстрактный (пустой) в данном классе метод,
реализующий один шаг сужения интервала изоляции.}
procedure OneStep;virtual;abstract;
//Метод инициализации
function Initialize (const aLeftPart:TLeftPart;
const xMin,xMax:double):TWhere;virtual;
{Члены класса, описанные с модификатором доступа public,
доступны всем объектам}
public
//Свойства, доступные только для чтения
//Левая часть уравнения
property LeftPart:TLeftPart read FLeftPart;
property CurrStep:double read FCurrStep; //Текущий интервал
property CurrX:double read FCurrX;
//Текущий аргумент
property CurrF:double read FCurrF;
//Текущая функция
//Текущий знак функции
property CurrSign:Shortint read FCurrSign;
//Текущий угол наклона функции на интервале изоляции (приближение)
property Derivative:double read GetDerivative;
//Текущее значение функции в средней точке интервала изоляции
property FunctionValue:double read GetFunctionValue;
//Свойства, доступные для записи и чтения
property Accuracy:double
read FAccuracy write SetAccuracy;//Погрешность
property EndFlag:TEndFlag read FEndFlag write FEndFlag;
{Основной метод, возвращающий корень уравнения на заданном
интервале изоляции. Параметры – левая часть уравнения,
исходный интервал изоляции, обработчик событий перед началом
сужения интервала, обработчик событий на каждом шаге сужения.}
function GetSolution(const aLeftPart:TLeftPart;
const xMin,xMax:double;
const OnStart:TNotifyEvent;
const OnStep:TOnStep):double;
constructor Create;//Конструктор объектов класса
end;
//Класс, реализующий сужение методом деления отрезка пополам
TTranscEqHalf=class(TTranscEquation)
private
FPrevSign:Shortint;
//Хранит предыдущий знак функции
protected
5
//Реализует метод сужения интервала
procedure OneStep;override;
function Initialize
//Метод инициализации
(const aLeftPart:TLeftPart;
const xMin,xMax:double):TWhere;override;
end;
//Класс, реализующий сужение методом секущих
TTranscEqNewton=class(TTranscEquation)
protected
procedure OneStep;override; //Реализует метод секущих
end;
На этом заканчивается интерфейсная секция модуля uTranscEquation.
В общем случае интерфейсная секция модуля в Delphi содержит описания
типов (служебное слово type), переменных (var), постоянных (const),
процедур (procedure) и функций (function). Описание дается в принципе в
произвольном порядке. Но, если, например, описание переменной или
постоянной включает в себя ссылку на некоторый тип, то этот тип должен быть
описан выше. Например, в нашем модуле есть описание постоянной
DefEndFlag:TEndFlag=efAcc;
Оно включает в себя ссылку на тип TEndFlag. Поэтому описание типа
TEndFlag располагается выше.
В этом правиле есть исключения. Так, в нашем модуле описан тип
TTranscEqClass=class of TTranscEquation;
И это описание включает в себя тип TTranscEquation, описанный ниже. Такое
допустимо.
В интерфейсной части нашего модуля uTranscEquation описаны некоторые
типы и постоянные, которые могут быть использованы в любом программном
модуле, ссылающемся на наш модуль. Комментарии к постоянным,
приведенные выше, достаточны, чтобы уяснить их смысл. Остановимся более
подробно на смысле описанных типов.
Типы – это указания на то, что представляют собой объекты (переменные), в
дальнейшем описанные этими типами. Существуют простые типы, такие как
double, Boolean и т.д. Они известны транслятору, и описывать их не надо.
Известна и структура объектов, относящихся к этим стандартным типам. Но
транслятор позволяет описывать свои, пользовательские типы. Имена
пользовательских типов в Delphi принято начинать с буквы T.
Типы TEndFlag и TWhere относятся к так называемым перечислимым типам.
Переменные
перечислимых
типов
имеют
значения,
описываемые
«говорящими» постоянными. В случае TEndFlag это постоянные efZero и
efAcc, в случае TWhere - Into, Min и Max. Под этими постоянными, конечно,
скрываются реальные числа – целые числа 0, 1 или 0, 1, 2. Но вместо чисел
бывает удобнее пользоваться обозначениями, имеющими простой смысл.
Тип TTranscEqClass описывает объекты, которые сами по себе являются
типами – типами класса TTranscEquation и его наследников. Такие типы
6
описываются в Delphi с помощью служебного словосочетания class of.
Смысл такого типа состоит в следующем.
Предположим, что мы намерены написать код либо приложения, либо просто
нового класса, в котором будут использоваться классы-наследники
TTranscEquation. Но при этом мы хотим, чтобы конкретный тип
используемого класса не был фиксирован заранее, а являлся бы переменной
величиной. В этом случае переменным объектом становится тип класса.
Именно тип такого объекта и описан под именем TTranscEqClass. Объекты
этого типа так же являются числами. Это адреса областей памяти, в которых
находятся так называемые таблицы виртуальных методов соответствующих
классов. Описание каждого класса индуцирует появление в памяти такой
таблицы.
Два типа TLeftPart и TOnStep имеют окончанием служебное словосочетание
of object. Оно означает, что описанные типы отвечают объектам, которые
будут содержать адреса методов класса. Эти методы должны иметь
определенную сигнатуру. А именно, методы типа TLeftPart должны быть
функциями с одним параметром типа double и должны возвращать значения
типа double. Методы типа TOnStep также должны быть функциями, но без
параметров и возвращать должны значения типа Boolean.
Типы TLeftPart и TOnStep нужны для того, чтобы классы приложений,
ссылающиеся на наш модуль и использующие класс TTranscEquation и его
наследников, могли описать свои функции – левые части трансцендентных
уравнений типа TLeftPart и обработчик события типа TOnStep. Обработчик
события - это метод, который пользователь может написать в своем классе с
тем, чтобы производить определенные действия (например, перерисовывать
график интерфейса, или проверять наличие вызовов, поступающих в
приложение от системных устройств – таймера, мышки, клавиатуры и т.п.) в
определенной точке кода, где обработчик вызывается. В данном случае
событие OnStep наступает на каждом шаге цикла при сужении интервала
изоляции корня трансцендентного уравнения. Это будет видно из реализации
кода класса TTranscEquation.
Далее в интерфейсе модуля дается описание трех классов – класса
TTranscEquation и двух его наследников TTranscEqHalf и TTranscEqNewton.
Любой тип класса является структурным типом, состоящим из членов класса.
Члены класса в Delphi подразделяются на поля, методы и свойства.
Поля – это переменные заданного типа, в которых собственно и хранится вся
статическая информация о конкретном объекте данного класса.
У класса TTranscEquation есть несколько полей.
Во-первых, это поля секции private, доступные только методам класса
TTranscEquation. Поле FLeftPart типа TLeftPart хранит указатель на метод,
реализующий левую часть уравнения (идентификаторы полей в Delphi принято
начинать буквой F от field – поле). Поле FAccuracy типа double хранит
текущее значение погрешности. Поле FEndFlag типа TEndFlag хранит текущее
значение переменной, определяющей условие окончания цикла поиска корня.
Поле FOnStart хранит текущий указатель на обработчик события (см. ниже),
7
наступающего перед входом в цикл. Тип этого обработчика TNotifyEvent
стандартный – метод, описанный как процедура с одним параметром типа
TObject. Тип TNotifyEvent в библиотечном модуле Classes. Наконец,
указатель на обработчик события, наступающего на каждом шаге цикла,
хранится в поле FOnStep.
Во-вторых, это поля, описанные в разделе protected. Эти поля доступны
только потомкам, но не внешним объектам. К ним относятся поля типа double,
хранящие текущие значения функции FCurrF, аргумента FCurrX и шага
сужения интервала изоляции FCurrStep, а также поле FCurrSign типа
Shortint, хранящее текущий знак функции.
Обычно поля класса не помещают в секцию public, где описаны члены класса,
доступные всем объектам.
Существуют другие члены класса - свойства. Они регулируют доступ к полям
класса и обычно описываются в секции public. Часть свойств класса
TTranscEquation, такие как LeftPart, CurrStep, CurrX, CurrF и CurrSign,
указывают своим описанием на то, что соответствующие им поля доступны
только для чтения. Два свойства Derivative и FunctionValue вообще не
имеют соответствующих полей, а лишь методы типа Get, возвращающие
значения этих свойств. Свойство Accuracy делает доступным поле FAccuracy
для чтения и для записи. Но запись производится не непосредственно, а с
помощью метода SetAccuracy. А вот свойство EndFlag позволяет и читать и
писать напрямую поле FEndFlag, как если бы это поле было описано в секции
public.
Методы класса меняют значения полей класса в соответствие с целью, ради
которой класс и создается.
Среди скрытых модификатором доступа private методов мы видим методы
GetDerivative и GetFunctionValue, возвращающие значения свойств
Derivative и FunctionValue, и метод SetAccuracy, устанавливающий
значение поля погрешности FAccuracy. Кроме того, в той же секции описан
метод BasicLoop, реализующий основной цикл сужения интервала изоляции.
В зависимости от своего предназначения методы класса могут быть
процедурами или функциями, иметь параметры, или не иметь их вовсе. Как
методы класса они всегда имеют один неявный параметр, являющийся текущим
объектом класса и обозначаемый в Delphi словом Self.
В секции protected класса TTranscEquation описаны еще два метода OneStep
и Initialize. Оба они относятся к так называемым виртуальным методам, о
чем говорит сопровождающее их заголовок служебное слово virtual.
Виртуальные методы класса могут иметь разное содержание у потомков.
Виртуальный метод OneStep является абстрактным. Он не содержит никакого
кода. Об этом говорит служебное слово abstract в его описании. Наличие
абстрактного метода делает весь класс TTranscEquation абстрактным, не
позволяя создавать объекты этого класса. Абстрактный класс существует ради
потомков. Потомки должны реализовать абстрактный метод OneStep.
Секция описания класса TTranscEquation с модификатором доступа public
содержит также описание метода GetSolution. Этот основной метод, который
8
должен возвращать решение уравнения с правой частью aLeftPart на
интервале изоляции XMin, XMax и с обработчиками OnStart и OnStep.
У любого класса есть конструктор и деструктор. Конструктор (принятое в
Delphi обозначение конструктора Create) вызывается при создании объекта
класса. Деструктор (принятое в Delphi обозначение деструктора Destroy) – при
уничтожении объекта класса. В языке Delphi класс может не содержать
описание конструктора и/или деструктора явно. В этом случае вызывается
конструктор и/или деструктор класса-предка. В классе TTranscEquation есть
описание конструктора. Оно расположено в секции public. Деструктором
класса TTranscEquation служит деструктор его предка – класса TObject.
Класс TObject является предком класса TTranscEquation по умолчанию. Это
означает, что класс TTranscEquation наследует все функциональные
способности класса TObject. Например, в классе TTranscEquation не описан
метод Free, вызывающий деструктор. Но этот метод является одним из членов
класса TObject и доступен всем его потомкам. Поэтому объекты класса
TTranscEquation могут вызывать метод Free.
В интерфейсной секции модуля uTranscEquation описаны еще два класса
TTanscEqHalf
и TTranscEqNewton. Их предком является класс
TTranscEquation. По правилам синтаксиса языка Delphi предок класса
указывается в его заголовке в скобках сразу после служебного слова class.
В классе TTanscEqHalf появилось новое поле FPrevSign типа Shortint в
разделе private. Оно хранит предыдущий знак функции в одной из граничных
точек интервала изоляции. В разделе protected этого же класса описаны
заголовки виртуальных методов OneStep и Initialize. Содержание этих
методов класс TTanscEqHalf обязуется изменить. Для этого используется
служебное слово override.
В классе TTanscEqNewton есть только заголовок метода OneStep, который
класс намерен перекрыть.
Код методов класса пишется в разделе реализации. Там же можно описывать
локальные для модуля и недоступные внешним модулям переменные,
процедуры и функции. В данном случае нам потребуются лишь реализации
методов классов TTranscEquation, TTanscEqHalf и TTanscEqNewton,
представленных в интерфейсной секции.
{В блоке реализации используются объекты из стандартных модулей
Math и SysUtils. Так, в модуле SysUtils описан класс Exception,
используемый в методе Initialize, а в модуле Math описана
постоянная MaxDouble, используемая в методе GetSolution.}
implementation uses SysUtils,Math;
constructor TTranscEquation.Create;
begin
inherited Create;
//Вызывает конструктор предка (TObject)
FEndFlag:=DefEndFlag;
//Инициализирует поля по умолчанию
Accuracy:=DefAccuracy;
end {Create};
function TTranscEquation.Initialize
9
(const aLeftPart:TLeftPart;const xMin,xMax:double):TWhere;
var xMaxF:double;
begin
//Следующие операторы порождают исключительные ситуации, если не
//выполняются определенные условия.
//Условие отсутствия указателя на метод. Равносильно условию
//@aLeftPart=nil
if not Assigned(aLeftPart) then
{Порождается исключительная ситуация,
которая возвращает сообщение.
Сообщение помещается в качестве параметра конструктора
стандартного класса Exception из модуля SysUtils.
Нормальное выполнение прерывается.
Управление передается на ближайший блок except.}
raise Exception.Create('Не задано уравнение!');
if xMin>=xMax then
raise Exception.Create(
'Левая граница должна быть меньше правой!');
FLeftPart:=aLeftPart;
FCurrF:=LeftPart(xMin);
if CurrF=0.0 then
begin
Result:=Min;Exit//Метод прерывается. Корень найден в точке xMin
end;
xMaxF:=LeftPart(xMax);
if xMaxF=0.0 then
begin
Result:=Max;Exit//Метод прерывается. Корень найден в точке xMax
end;
FCurrSign:=1;if CurrF<0.0 then FCurrSign:=-1;
if CurrSign*xMaxF>0.0 then
raise
Exception.Create('Знаки на границах интервала одинаковы!');
FCurrX:=xMin;FCurrStep:=0.5*(xMax-xMin);
Result:=Into;
end {Initialize};
function TTranscEquation.BasicLoop:double;
var fTerm:Boolean;
begin
//В этой точке вызывается обработчик FOnStart, если он задан
if Assigned(FOnStart) then FOnStart(Self);
repeat
OneStep;//Вызывается метод сужения интервала изоляции
//Вырабатывается условие окончания цикла
fTerm:=(EndFlag=efZero) and (CurrStep=0.0) or
(EndFlag=efAcc) and
((abs(CurrStep)<Accuracy*abs(CurrX)) or
(abs(CurrStep)<Accuracy) and (abs(CurrX)<Accuracy));
//Цикл завершается либо выполнением требуемого условия,
//либо командой обработчика FOnStep (если обработчик задан)
until fTerm or Assigned(FOnStep) and FOnStep(Self);
Result:=CurrX //Метод возвращает текущее значение аргумента
end {BasicLoop};
10
function TTranscEquation.GetSolution
(const aLeftPart:TLeftPart;const xMin,xMax:double;
const OnStart:TNotifyEvent;const OnStep:TOnStep):double;
begin
FOnStart:=OnStart;FOnStep:=OnStep;//Устанавливаются обработчики
//Выполняется метод инициализации
case Initialize(aLeftPart,xMin,xMax) of
Into:Result:=BasicLoop;//Вызывается метод, сужающий интервал
Max:Result:=xMax;
Min:Result:=xMin;
//Оператор, который не должен выполняться
else Result:= Math.MaxDouble;
end;
end {GetSolution};
procedure TTranscEquation.SetAccuracy(const value:double);
begin
//Проверяется заданная погрешность. Если она выходит за интервал
//то метод устанавливает ее значение на соответствующую границу
if value<MinAccuracy then FAccuracy:=MinAccuracy else
if value>MaxAccuracy then FAccuracy:=MaxAccuracy else
FAccuracy:=value
end {SetAccuracy};
function TTranscEquation.GetDerivative:double;
begin
Result:=arctan(abs((LeftPart(CurrX+0.5*CurrStep)LeftPart(CurrX-0.5*CurrStep))/CurrStep))/(0.5*Pi);
end {GetDerivative};
function TTranscEquation.GetFunctionValue:double;
begin
Result:=0.5*(LeftPart(CurrX+0.5*CurrStep)+
LeftPart(CurrX-0.5*CurrStep));
end {GetFunctionValue};
function TTranscEqHalf.Initialize
(const aLeftPart:TLeftPart;const xMin,xMax:double):TWhere;
begin
//Виртуальный метод. В начале вызывает метод предка,
//а затем инициализирует одно из полей объекта
Result:=inherited Initialize(aLeftPart,xMin,xMax);
FPrevSign:=CurrSign;
end {Initialize};
//Реализация шага методом деления отрезка пополам
procedure TTranscEqHalf.OneStep;
begin
FCurrX:=CurrX+CurrStep;
FCurrF:=LeftPart(CurrX);
if CurrF=0.0 then
begin
FCurrStep:=0.0;Exit
11
end;
if CurrF<0 then FCurrSign:=-1 else FCurrSign:=1;
if FPrevSign<>CurrSign then FCurrStep:=-0.5*CurrStep
else FCurrStep:=0.5*CurrStep;
FPrevSign:=CurrSign;
end {OneStep};
//Реализация шага методом секущей
procedure TTranscEqNewton.OneStep;
begin
FCurrX:=CurrX+CurrStep;
FCurrF:=LeftPart(CurrX);
if CurrF=0.0 then
begin
FCurrStep:=0.0;Exit
end;
FCurrStep:=0.5*CurrStep;
if CurrF*CurrSign<0 then
begin
FCurrSign:=-CurrSign;
FCurrStep:=-CurrStep
end;
end {OneStep};
end.
C++
Теперь рассмотрим версию тех же классов численного решения
трансцендентного уравнения, написанную на языке C++ в среде C++ Builder (6ая версия) фирмы Borland.
Структура программного модуля в C++ несколько отличается от структуры
модуля, написанного на Object Pascal в Delphi. В определенной степени можно
сказать, что интерфейсной секции дельфийского модуля соответствует
отдельный физический файл программного модуля на C++, именуемый
«хэдер», или файл заголовков. Хэдер имеет расширение .h. Другой файл,
имеющий расширение .cpp и то же имя, содержит обычно то, что соответствует
секции реализации в Delphi. Оба файла образуют как бы неразрывную пару,
являющуюся программным модулем типа unit в Delphi.
В начале рассмотрим подробнее содержание хэдера классов решения
трансцендентных уравнений.
В данном случае модуль имеет имя uTranscEquation. Весь код хэдера заключен
«в скобки» защитного блокиратора вида
#ifndef uTranscEquationH
#define uTranscEquationH
Код хэдера
#endif
Дело в том, что хэдер в процессе компиляции становится частью общего текста
модуля с тем же именем. Он объединяется со второй частью модуля – файлом с
12
тем же именем и расширением .cpp. Последний файл для этого содержит
специальную директиву компилятора вида #include «имя хэдера» (см.
ниже). Блокиратор же предохраняет такое соединение двух файлов от
дублирования информации, содержащейся в хэдере. Для этого в блокираторе с
помощью директивы компилятора #define определяется некоторый символ,
представляющий имя файла, к которому добавлена большая буква H. В нашем
случае это символ uTranscEquationH. При первом обращении к хэдеру этот
символ не определен и условная директива компилятора #ifndef … (если не
определен символ …) позволяет выполняться всем операторам вплоть до
завершающей директивы #endif. Так как среди этих операторов имеется
директива #define, определяющая этот символ, то при любой попытке
повторно использовать код хэдера условие #ifndef не выполнится и код не
будет подшит.
Изучая код хэдера, обратите внимание на следующие отличия в синтаксисе
описания на языках Delphi и C++:
1. В отличие от Delphi, описание каждого типа отдельно должно
предваряться служебным словом typedef.
2. В описании на языке C в начале указывается тип, а затем имя переменной
или типа. Например, в начале указывается перечислимый тип enum
{efZero,efAcc}, а затем имя типа TEndFlag. Другой пример – описание
постоянной DefEndFlag типа TEndFlag.
3. Тип метода класса формируется с помощью двух модификаторов доступа
__fastcall и __closure. Модификатор __fastcall указывает на то, что
вызов метода должен быть оптимизирован транслятором с целью
наибыстрейшего доступа к его параметрам. Модификатор __closure
специфичен именно для C++ Builder. Он указывает на то, что следующий
далее указатель на функцию (к примеру, *TLeftPart в нашем модуле)
должен рассматриваться как указатель на метод класса. То, что речь
идет об указателе, отмечается стандартным для C символом звездочки *
(астериск).
4. При описании класса предок указывается через двоеточия после имени
наследника как в выражении class TTranscEquation: public
TObject. Указание на предка в описании класса может содержать
модификатор доступа типа public как в данном случае. Это означает, что
модификаторы доступа наследуемых членов класса либо остаются у
наследника такими же, как и у предка (модификатор public), либо их
уровень доступа снижается (модификаторы protected и private). В
случае protected уровень public у предка снижается до protected у
наследника, а в случае private уровни public и protected у предка
снижаются до private у наследника. Если модификатор не указан,
транслятор подставляет private.
5. Модификаторы доступа к членам класса private, protected, public
должны завершаться двоеточием.
13
6. В языке C нет процедур. Если функция не возвращает переменную
какого-либо типа, то возвращаемый тип именуется void как в
__fastcall void SetAccuracy(double const value);.
7. Даже если у функции отсутствуют параметры, скобки все равно пишутся
как, например, в __fastcall double BasicLoop();.
8. Модификатор virtual указывается впереди имени метода как в
__fastcall virtual void OneStep()=0;. В этом же примере
инициализатор метода =0 означает, что метод является абстрактным и
не содержит тела.
9. Обратите внимание на отличия в описании свойств.
10.При повторном описании виртуального метода в классе-наследнике
модификатор доступа override не используется.
11.Конструктор объектов в C++ это метод с тем же именем, что и класс.
Конструктор не возвращает значение какого-либо типа, даже void.
12.При написании идентификаторов в языке C следует учитывать их
зависимость от того, используется прописная или строчная буквы. Так
идентификаторы currF и CurrF различаются. Это используется при
описании полей и соответствующих свойств.
#ifndef uTranscEquationH
#define uTranscEquationH
//-------------------------------------------------------------------------typedef enum {efZero,efAcc} TEndFlag;
typedef double __fastcall(__closure *TLeftPart)(double);
typedef bool __fastcall(__closure *TOnStep)( TObject* sender);
typedef enum {Into,Min,Max} TWhere;
TEndFlag const DefEndFlag=efAcc;
double const
MinAccuracy=1e-18,
MaxAccuracy=0.01,
DefAccuracy=MinAccuracy;
class TTranscEquation:TObject
{
private:
TLeftPart leftPart;
double accuracy;
TEndFlag endFlag;
TNotifyEvent onStart;
TOnStep onStep;
__fastcall double GetDerivative();
__fastcall double GetFunctionValue();
__fastcall void SetAccuracy(double const value);
__fastcall double BasicLoop();
protected:
double currF,currX,currStep;
short int currSign;
__fastcall virtual void OneStep()=0;
__fastcall virtual TWhere Initialize(
TLeftPart const aLeftPart,
14
double const xMin,double const xMax);
public:
__property TLeftPart LeftPart= {read=leftPart};
__property double CurrStep= {read=currStep};
__property double CurrX={read=currX};
__property double CurrF={read=currF};
__property short int CurrSign={read= currSign};
__property double Accuracy=
{read=accuracy, write=SetAccuracy};
__property TEndFlag EndFlag= {read=endFlag, write=endFlag};
__property double Derivative= {read=GetDerivative};
__property double FunctionValue= {read=GetFunctionValue};
__fastcall TTranscEquation();//Конструктор объектов
__fastcall double GetSolution(TLeftPart const aLeftPart,
double const xMin,double const xMax,
TNotifyEvent const OnStart,TOnStep const OnStep);
};
class TTranscEqHalf:public TTranscEquation
{
private:
short int prevSign;
protected:
__fastcall void OneStep();
__fastcall TWhere Initialize(TLeftPart const aLeftPart,
double const xMin,double const xMax);
public:
__fastcall TTranscEqHalf();//Конструктор объектов
};
class TTranscEqNewton:public TTranscEquation
{
protected:
__fastcall void OneStep();
public:
__fastcall TTranscEqNewton();//Конструктор объектов
};
#endif
Существенным отличием хэдера от интерфейсной секции в Delphi является то,
что в хэдере возможна реализация методов класса, а в интерфейсной секции
Delphi – нет. В описании настоящего класса все реализации методов
помещаются в файл исходника .cpp. Однако, для примера, попытайтесь
поместить реализацию одного из методов (например, конструктора) в хэдер.
Перейдем теперь к анализу кода, который размещается в файле-исходинке
uTranscEquation.cpp.
#include <vcl.h> //Модуль, несущий определения библиотеки VCL
/*Директива #pragma hdrstop означает окончание списка хэдеров,
компилируемых предварительно для использования в нескольких
файлах-исходниках одного проекта. В данном случае в этом списке
есть только файл vcl.h.
15
Эта директива автоматически добавляется средой.*/
#pragma hdrstop
#include <math.h>
//Хэдер одной из стандартных библиотек
#include "uTranscEquation.h" //хэдер нашего исходника
#include "values.h"
//Хэдер одной из стандартных библиотек
/*Директива #pragma package(smart_init) служит для «разумной»
последовательности в инициализации модулей при формировании
кода проекта. Она также автоматически добавляется средой
при создании нового модуля.*/
#pragma package(smart_init)
/*Далее располагается собственно авторский код.
В начале дается код конструктора класса.
Любой метод класса должен иметь в заголовке
имя класса, отделенного от имени самого метода
двойным двоеточием. В Delpi это была точка.
После имени конструктора через двоеточие
указывается имя конструктора предка, который следует
вызвать в самом начале.
После этого в фигурных скобках
(в Delphi это begin end) помещается код метода*/
__fastcall TTranscEquation::TTranscEquation():TObject()
{
//поля инициализируются их значениями по умолчанию
endFlag=DefEndFlag;
Accuracy=DefAccuracy;
}
TWhere __fastcall TTranscEquation::Initialize(
TLeftPart const aLeftPart,
double const xMin,double const xMax)
{
if (!aLeftPart) //Условие оператора if всегда пишется в скобках.
//Проверку указателя можно проводить, используя
//оператор ! (логическое «нет»), как в коде.
//Можно также сравнивать с «указателем в никуда» NULL.
//Тогда эквивалентным будет условие (aLeftPart==NULL)
/*Служебное слово throw используется для создания
исключительной ситуации.
После этого нормальный ход программы прерывается.
Управление передается на ближайший блок catch.*/
throw Exception("Уравнение не задано!");
if (xMin>=xMax)
throw Exception("Левая граница больше или равна правой!");
leftPart=aLeftPart;
currF=LeftPart(xMin);
//В языке C локальные переменные могут быть описаны в любой
точке кода
double xMaxF=LeftPart(xMax);
//Служебное слово return приводит к завершению метода
if (CurrF==0.0) return Min;
if (xMaxF==0.0) return Max;
/*В языке C есть так называемое условное выражение,
которого нет в Паскале.
16
С помощью него можно более кратко записывать условия,
возвращающие значения переменных (сравните с кодом в Delphi).*/
currSign= CurrF<0.0?-1:1;
if (CurrSign*xMaxF>0.0)
throw Exception("Это не интервал изоляции!");
currX=xMin;currStep=0.5*(xMax-xMin);
return Into;
}
double __fastcall TTranscEquation::BasicLoop()
{
bool fTerm;
//Служебное слово this означает текущий объект данного класса.
//Условие if (onStart) означает в данном случае
//– «если обработчик onStart задан»
if (onStart) onStart(this);
do
{
//В цикле вызывается метод OneStep сужения интервала изоляции
OneStep();
//Формируется условие окончания цикла
fTerm= EndFlag==efZero && CurrStep==0.0 ||
EndFlag==efAcc && (fabsl(CurrStep)<Accuracy*fabsl(CurrX) ||
fabsl(CurrStep)<Accuracy && fabsl(CurrX)<Accuracy);
}
/*На каждом шаге цикла вызывается обработчик onStep,
если он задан. Если onStep возвращает true,
то цикл прерывается.
Обратите внимание на то, как преобразуются типы в аргументе
обработчика событий onStep. Аналогичное, но обратное
преобразование следует совершать в приложении, где
такой обработчик задается другим классом. Тогда код
этого обработчика при попытке использовать какое-либо свойство
(пусть это будет метод класса AnyClass с именем OnStep и в нем
требуется использовать свойство CurrX «сендера» - наследника
класса TTranscEquation)
должен будет обращаться с аргументом sender следующим образом
bool __fastcall AnyClass::OnStep( TObject* sender);
{
…
…((TTranscEquation*)sender)->CurrX…
…
}
*/
while (!fTerm && !(onStep && onStep((TObject*) this)));
return CurrX;
}
//Основной метод, возвращающий решение уравнения
double __fastcall TTranscEquation::GetSolution(
TLeftPart const aLeftPart,
double const xMin,double const xMax,
TNotifyEvent const OnStart,TOnStep const OnStep)
{
//Поля обработчиков получают свои значения: ссылки на обработчики
17
onStart=OnStart;onStep=OnStep;
/*Условный оператор switch – переключатель.
В нем вызывается метод инициализации.
В зависимости от результата этого метода
либо включается основной цикл поиска корня,
который возвращает корень,
либо, если корень на одной из границ интервала –
возвращается значение
редполагается, что какая-либо иная ситуация невозможна, однако
в иной ситуации возвращается максимальное число типа double */
switch (Initialize(aLeftPart,xMin,xMax))
{
case Into:return BasicLoop();
case Min:return xMin;
case Max:return xMax;
default: return MAXDOUBLE;
}
}
void __fastcall TTranscEquation::SetAccuracy(double const value)
{
accuracy=
value<MinAccuracy?MinAccuracy:value>MaxAccuracy?MaxAccuracy:value;
}
double __fastcall TTranscEquation::GetDerivative()
{
return atanl(fabsl((LeftPart(CurrX+0.5*CurrStep)LeftPart(CurrX-0.5*CurrStep))/CurrStep))/(0.5*M_PI);
}
double __fastcall TTranscEquation::GetFunctionValue()
{
return 0.5*(LeftPart(CurrX+0.5*CurrStep)+
LeftPart(CurrX-0.5*CurrStep));
}
/*Даже, если конструктор наследника ничего не добавляет
к конструктору предка, его все равно следует описывать
явно и вызывать в нем конструктор предка,
в отличие от Delphi.*/
__fastcall TTranscEqHalf::TTranscEqHalf():TTranscEquation()
{}
void __fastcall TTranscEqHalf::OneStep()
{
//Оператор типа += в языке C сокращает запись
//типа currX= currX+CurrStep
currX+=CurrStep;
currF=LeftPart(CurrX);
if (CurrF==0.0)
{
currStep=0.0;return;
}
18
currSign=currF<0?-1:1;
currStep=prevSign!=CurrSign?-0.5*CurrStep:0.5*CurrStep;
prevSign=CurrSign;
}
TWhere __fastcall TTranscEqHalf::Initialize(
TLeftPart const aLeftPart,
double const xMin,double const xMax)
{
//Так вызывается виртуальный метод предка
TWhere Result=TTranscEquation::Initialize(aLeftPart,xMin,xMax);
prevSign=CurrSign;
return Result;
}
__fastcall TTranscEqNewton::TTranscEqNewton():TTranscEquation()
{}
void __fastcall TTranscEqNewton::OneStep()
{
currX+=CurrStep;
currF=LeftPart(CurrX);
if (CurrF==0.0)
{
currStep=0.0;return;
}
currStep=0.5*CurrStep;
if (CurrF*CurrSign<0)
{
currSign=-CurrSign;
currStep=-CurrStep;
}
}
C#
В языке C# компилируемый модуль является отдельным файлом и содержит в
себе сразу и описание, и реализацию методов класса. Хэдеры отсутствуют.
Последовательность описания членов класса не имеет значения. Более того,
такой модуль легко скомпилировать в форме отдельного исполняемого модуля
с расширением .dll (dynamic link library). Правда, в отличие, от exe-файла
динамически загружаемая библиотека не имеет точки входа и не может
выполняться незавиисмо от вызывающего приложения.
Весь код в C# разбит на пространства имен. Обычно отдельный
компилируемый модуль относится к одному пространству имен, которое
указывается в заголовке модуля. Хотя это не правило.
В начале модуля может находиться список используемых пространств имен
(служебное слово using ставится перед каждым именем пространства имен в
списке). В нашем случае в этом списке есть только одно имя System.
Использование каких-либо идентификаторов из пространства имен System в
19
нашем модуле не потребует написания полного имени. Например, класс Math,
используемый в нашем пространстве имен TranscEquation, описан в модуле
System. Использование директивы using System позволяет вызывать внутри
модуля класс Math его кратким именем, а не полным System.Math.
Директивы компилятора #region и #endregion выделяют поименованную
область (в данном случае с именем Using directives), которая может быть
свернута в тексте редактора кода для облегчения чтения кода. Естественно, эти
директивы не обязательны и могут быть исключены из текста.
В языке C# все типы являются классами – наследниками одного общего для
всех класса Object. В частности это относится ко всем простым типам int,
double и т.д. Это так называемые типы-значения. К типам-значениям
относится также перечислимый тип enum. Другой распространенный тип
классов обозначается служебным словом delegate. Он позволяет описать
обработчик событий или указатель на любой метод класса. Из предыдущего
контекста в Delphi и C++ должен быть понятен смысл классов типа delegate.
#region Using directives
using System;
#endregion
namespace TranscEquation
{
public enum TEndFlag { efZero, efAcc } ;
public delegate double TLeftPart(double x);
public delegate void StartEventHandler(object sender);
public delegate bool OnStepEventHandler(object sender);
/*В C# абстрактность класса указывается в его описании.
Если класс является прямым наследником Object, то,
как в Delphi,предка класса можно не указывать в его описании*/
public abstract class TranscEquation
{
//Описание постоянных вне класса в C# недопустимо
//В C# существует особенность в использовании
// переменных типа enum - необходимо указывать их тип.
public const TEndFlag DefEndFlag = TEndFlag.efAcc;
public const double
MinAccuracy = 1e-18,
MaxAccuracy = 0.01,
DefAccuracy = MinAccuracy;
//Отсутствие модификатора доступа типа public или protected
//в описании поля, метода или свойства класса означает,
//что это модификатор private.
TLeftPart leftPart;
double accuracy;
TEndFlag endFlag;
StartEventHandler onStart;
OnStepEventHandler onStep;
protected double currF, currX, currStep;
protected int currSign;
20
//В описании абстрактного метода указывается явно
// модификатор abstract
protected abstract void OneStep();
//Так выглядят описания свойств в C#.
//Явно определяются методы set и/или get и их содержание.
public TLeftPart LeftPart
{
get { return leftPart; }
}
public double CurrStep { get { return currStep; } }
public double CurrX { get { return currX; } }
public double CurrF { get { return currF; } }
public int CurrSign { get { return currSign; } }
//Служебное слово value играет роль параметра в методе set.
public double Accuracy
{
get { return accuracy; }
set
{
accuracy =
value < MinAccuracy ? MinAccuracy :
value > MaxAccuracy ? MaxAccuracy : value;
}
}
public TEndFlag EndFlag
{
get { return endFlag; }
set { endFlag = value; }
}
public double Derivative
{
get
{
return Math.Atan(Math.Abs(
(LeftPart(CurrX + 0.5 * CurrStep) –
LeftPart(CurrX - 0.5 * CurrStep)) / CurrStep))
/ (0.5 * Math.PI);
}
}
public double FunctionValue
{
get
{
return 0.5 * (LeftPart(CurrX + 0.5 * CurrStep) +
LeftPart(CurrX - 0.5 * CurrStep));
}
}
protected enum TWhere { Into, Min, Max } ;
double BasicLoop()
{
bool fTerm;
/*В отличие от C++ в языке C# нельзя заменить неравенство
вида onStart != null неравенством !onStart.*/
if (onStart != null) onStart(this);
21
do
{
OneStep();
fTerm = EndFlag == TEndFlag.efZero &&
CurrStep == 0.0 ||
EndFlag == TEndFlag.efAcc &&
(Math.Abs(CurrStep) < Accuracy * Math.Abs(CurrX) ||
Math.Abs(CurrStep) < Accuracy
&& Math.Abs(CurrX) < Accuracy);
}
while (!fTerm && !(onStep != null && onStep(this)));
return CurrX;
}
protected virtual TWhere Initialize(TLeftPart aLeftPart,
double xMin, double xMax)
{
//Обратите внимание на особенности порождения
//исключительной ситуации в C# по сравнению с C.
if (aLeftPart == null) throw (new ApplicationException
("Уравнение не задано!"));
if (xMin >= xMax) throw (new ApplicationException
("Левая граница больше или равна правой!"));
leftPart = aLeftPart;
currF = LeftPart(xMin);
double xMaxF = LeftPart(xMax);
if (CurrF == 0.0) return TWhere.Min;
if (xMaxF == 0.0) return TWhere.Max;
currSign = CurrF < 0.0 ? -1 : 1;
if (CurrSign * xMaxF > 0.0)
throw (new ApplicationException(
"Это не интервал изоляции!"));
currX = xMin; currStep = 0.5 * (xMax - xMin);
return TWhere.Into;
}
public double GetSolution(TLeftPart aLeftPart,
double xMin, double xMax,
StartEventHandler OnStart, OnStepEventHandler OnStep)
{
onStart = OnStart; onStep = OnStep;
switch (Initialize(aLeftPart, xMin, xMax))
{
case TWhere.Into: return BasicLoop();
case TWhere.Min: return xMin;
case TWhere.Max: return xMax;
/*У класса Double в C# существует свойство NaN (Not-a-Number).
Оно возвращает число, не являющееся вещественным числом, хотя
относится к классу double.*/
default: return Double.NaN;
}
}
/*В языке C# существует модификатор доступа protected internal.
Он означает, что метод доступен только наследникам, описанным
в том же приложении, что и класс.
Как и в C++ каждый класс должен иметь конструктор
22
с именем класса.
Но в отличие от C++ конструктор может иметь доступ,
отличный от public.*/
protected internal TranscEquation()
{
endFlag = DefEndFlag;
Accuracy = DefAccuracy;
}
}
//Предком класса TranscEqHalf является TranscEquation
public class TranscEqHalf:TranscEquation
{
public int prevSign;
//Как и в Delphi виртуальный метод переопределяется
//служебным словом override
protected override void OneStep()
{
currX += CurrStep;
currF = LeftPart(CurrX);
if (CurrF == 0.0)
{
currStep = 0.0; return;
}
currSign = currF < 0 ? -1 : 1;
currStep = prevSign != CurrSign ? -0.5 * CurrStep :
0.5 * CurrStep;
prevSign = CurrSign;
}
protected override TWhere Initialize(TLeftPart aLeftPart,
double xMin, double xMax)
{
//Метод предка вызывается с помощью служебного слова base
TWhere Result = base.Initialize(aLeftPart,
xMin, xMax);
prevSign = CurrSign;
return Result;
}
//С помощью служебного слова base вызывается конструктор предка
public TranscEqHalf():base () { }
}
public class TranscEqNewton : TranscEquation
{
protected override void OneStep()
{
currX += CurrStep;
currF = LeftPart(CurrX);
if (CurrF == 0.0)
{
currStep = 0.0; return;
}
currStep = 0.5 * CurrStep;
if (CurrF * CurrSign < 0)
{
currSign = -CurrSign;
23
currStep = -CurrStep;
}
}
public TranscEqNewton():base () { }
}
}
Задание. Используя приведенные классы, составьте приложения в каждой из
сред, графически иллюстрирующие процесс определения корней каких-либо
трансцендентных уравнений.
Класс численного интегрирования
Рассмотрим пример другого класса – вычисление определенного интеграла
методом Симпсона. Обратите внимание на алгоритм реализации метода
Симпсона в форме рекурсивного метода TSimpson.Simpson. Интервал
интегрирования разбивается на три равные части, далее каждая из частей еще
на три и т.д. Поле FLevel хранит текущий уровень разбиения. Процесс
завершается либо при выполнении условия приближения (поле FAccuracy),
либо по достижении максимально допустимого уровня (поле FNMax), либо,
наконец, при возвращении обработчиком OnStep значения true. Указатель на
метод, определяющий интегрируемую функцию, хранится в поле Ff, а пределы
интегрирования – в полях Fa и Fb. Значения полей FNMax и FAccuracy
устанавливаются конструктором класса по умолчанию и свойствами NMax и
Accuracy.
Основным методом интегрирования, доступным пользователю, является метод
Integral, параметры которого указывают на интегрируемую функцию af типа
TFunction, определяют пределы интегрирования aa,ab и ссылку на
обработчик событий OnStep, срабатывающий при каждом последовательном
изменении уровня разбиения промежутка интегрирования.
Delphi
unit uTSimpson;
interface
const
//Максимально допустимый уровень разбиения
// интервала интегрирования по умолчанию
DefNMax=100;
MinAcc=1e-15;
MaxAcc=0.01;
DefAcc=1e-6;
type
//Типы применяемых обработчиков
TFunction=function (x:double):double of object;
TOnStep=function(sender:TObject):Boolean of object;
TSimpson=class
private
FNMax,FLevel:word;
Fa,Fb,FAccuracy:double;
Ff:TFunction;
24
procedure SetAccuracy(const value:double);
procedure SetNMax(const value:word);
function Simpson(Cura,da,fa,fm,fb,AbsArea,est,Acc:double;
const OnStep:TOnStep):double;
public
property NMax:word read FNMax write SetNMax;
property Level:word read FLevel;
property Accuracy:double read FAccuracy write SetAccuracy;
property a:double read Fa;
property b:double read Fb;
property f:TFunction read Ff;
constructor Create;
function Integral(const af:TFunction;const aa,ab:double;
const OnStep:TOnStep):double;
end;
implementation uses SysUtils;
constructor TSimpson.Create;
begin
inherited;
Accuracy:=DefAcc;
NMax:=DefNMax
end {Create};
function TSimpson.Integral(const af:TFunction;const aa,ab:double;
const OnStep:TOnStep):double;
begin
if not Assigned(af) then
begin
raise Exception.Create('Функция не задана!');Exit
end;
Ff:=af;Fa:=aa;Fb:=ab;
FLevel:=1;
Result:=Simpson(a,b-a,f(a),4.0*f(0.5*(a+b)),
f(b),1.0,1.0,Accuracy,OnStep)
end {Integral};
procedure TSimpson.SetAccuracy(const value:double);
begin
if value<MinAcc then FAccuracy:=MinAcc else
if value>MaxAcc then FAccuracy:=MaxAcc else
FAccuracy:=value
end {SetAccuracy};
procedure TSimpson.SetNMax(const value:word);
begin
if value>0 then FNMax:=value;
end {SetNMax};
function
TSimpson.Simpson(Cura,da,fa,fm,fb,AbsArea,est,Acc:double;
const OnStep:TOnStep):double;
var dx,dx6,est1,est2,est3,f1,f2,f3,f4,sum,x1,x2:double;
begin
dx:=da*0.333333333333333333333333;
25
x1:=Cura+dx;f1:=4.0*f(Cura+0.5*dx);f2:=f(x1);
x2:=x1+dx; f3:=f(x2);f4:=4.0*f(Cura+2.5*dx);
dx6:=dx/6.0;
est1:=(fa+f1+f2)*dx6;
est2:=(f2+fm+f3)*dx6;
est3:=(f3+f4+fb)*dx6;
AbsArea:=AbsArea-Abs(est)+Abs(est1)+Abs(est2)+Abs(est3);
Sum:=est1+est2+est3;
Inc(FLevel);
if (Abs(Est-Sum)<=Acc*AbsArea) and (Est<>1.0) or (Level>=NMax)
or Assigned(OnStep) and OnStep(Self)
then Result:=Sum else
Result:=
Simpson(Cura,dx,fa,f1,f2,AbsArea,est1,0.577*Acc,OnStep)+
Simpson(x1,dx,f2,fm,f3,AbsArea,est2,0.577*Acc,OnStep)+
Simpson(x2,dx,f3,f4,fb,AbsArea,est3,0.577*Acc,OnStep);
Dec(FLevel)
end {Simpson};
end {uTSimpson}.
C++
В версии C++ код того же класса в модуле uTSimpson.
#include "math.h"
#ifndef uTSimpsonH
#define uTSimpsonH
typedef double __fastcall (__closure *TFunction)(double x);
double const DefAcc=1e-6,MinAcc=1e-15,MaxAcc=0.01;
unsigned int const DefNMax=100;
typedef bool __fastcall (__closure *TOnStep)(TObject *sender);
class TSimpson
{
private:
double a,b, accuracy;
unsigned int nMax;
TFunction f;
unsigned int level;
double __fastcall Simpson(double Cura,double da,
double fa,double fm,double fb,double AbsArea,
double est,double Acc,TOnStep OnStep);
void SetAccuracy(double const value);
void SetNMax(unsigned int const value);
public:
__property double Accuracy={read=accuracy,write=SetAccuracy};
__property unsigned int NMax={read=nMax,write=SetNMax};
__property unsigned int Level={read=level};
__fastcall TSimpson(unsigned int const aNMax=DefNMax,
double const aAccuracy=DefAcc)
{
Accuracy=aAccuracy;NMax=aNMax;
}
26
double __fastcall Integral(TFunction af,double aa,double ab,
TOnStep OnStep);
};
#endif
#include <vcl.h>
#pragma hdrstop
#include "uTSimpson.h"
#pragma package(smart_init)
void TSimpson::SetAccuracy(double const value)
{
accuracy=value<MinAcc?MinAcc:value>MaxAcc?MaxAcc:value;
}
void TSimpson::SetNMax(unsigned int const value)
{
if (value>0) nMax=value;
else
throw Exception("Число разбиений равно нулю?!");
}
double __fastcall TSimpson::Integral(TFunction af,double aa,
double ab, TOnStep OnStep)
{
if (!af)
throw Exception("Функция не задана!");
a=aa;f=af;b=ab;
level=1;
return Simpson(a,b-a,f(a),4.0*f(0.5*(a+b)),
f(b),1.0,1.0,Accuracy,OnStep);
}
double __fastcall TSimpson::Simpson(double Cura,double da,
double fa,double fm,double fb,double AbsArea,
double est,double Acc,TOnStep OnStep)
{
double dx,dx6,est1,est2,est3,f1,f2,f3,f4,sum,x1,x2,Result;
dx=da*0.333333333333333333333333;
x1=Cura+dx;f1=4.0*f(Cura+0.5*dx);f2=f(x1);
x2=x1+dx; f3=f(x2);f4=4.0*f(Cura+2.5*dx);
dx6=dx/6.0;
est1=(fa+f1+f2)*dx6;
est2=(f2+fm+f3)*dx6;
est3=(f3+f4+fb)*dx6;
AbsArea+=-fabsl(est)+fabsl(est1)+fabsl(est2)+fabsl(est3);
sum=est1+est2+est3;
level++;
Result=(fabsl(est-sum)<=Acc*AbsArea && est!=1.0 || Level>=NMax
|| OnStep && OnStep((TObject*)this))?sum:
Simpson(Cura,dx,fa,f1,f2,AbsArea,est1,0.577*Acc,OnStep)+
Simpson(x1,dx,f2,fm,f3,AbsArea,est2,0.577*Acc,OnStep)+
Simpson(x2,dx,f3,f4,fb,AbsArea,est3,0.577*Acc,OnStep);
27
level--;
return Result;
}
C#
#region Using directives
using System;
#endregion
namespace Simpson
{
public delegate double Function(double x);
public delegate bool OnStepEventHandler(object sender);
public class TSimpson
{
public const double
DefAcc = 1e-6,
MinAcc = 1e-15,
MaxAcc = 0.01;
public const uint DefNMax = 100;
double a, b, accuracy;
uint nMax, level;
Function f;
double Simpson(double Cura, double da,
double fa, double fm, double fb, double AbsArea,
double est, double Acc,OnStepEventHandler OnStep)
{
double dx, dx6, est1, est2, est3, f1, f2, f3, f4,
sum, x1, x2, Result;
dx = da * 0.333333333333333333333333;
x1 = Cura + dx; f1 = 4.0 * f(Cura + 0.5 * dx);
f2 = f(x1);
x2 = x1 + dx; f3 = f(x2);
f4 = 4.0 * f(Cura + 2.5 * dx);
dx6 = dx / 6.0;
est1 = (fa + f1 + f2) * dx6;
est2 = (f2 + fm + f3) * dx6;
est3 = (f3 + f4 + fb) * dx6;
AbsArea += - Math.Abs(est) + Math.Abs(est1) +
Math.Abs(est2) + Math.Abs(est3);
sum = est1 + est2 + est3;
level++;
Result = (Math.Abs(est - sum) <= Acc * AbsArea &&
est != 1.0 || Level >= NMax ||
OnStep != null && OnStep(this))? sum:
Simpson(Cura,dx,fa,f1,f2,AbsArea,est1,0.577*Acc,OnStep)+
Simpson(x1,dx,f2,fm,f3,AbsArea,est2,0.577*Acc,OnStep)+
Simpson(x2,dx,f3,f4,fb,AbsArea,est3,0.577*Acc,OnStep);
level--;
return Result;
}
28
public double Accuracy
{
get { return accuracy; }
set
{
accuracy=value<MinAcc?MinAcc:value>MaxAcc?MaxAcc:value;
}
}
public uint NMax
{
get { return nMax; }
set
{
if (value > 0) nMax = value;
else throw (new ApplicationException
("Число разбиений равно нулю?!"));
}
}
public uint Level
{
get { return level; }
}
public TSimpson(uint aNMax, double aAccuracy)
{
Accuracy = aAccuracy; NMax = aNMax;
}
public double Integral(Function af, double aa, double ab,
OnStepEventHandler OnStep)
{
if (af == null)
throw (new ApplicationException("Функция не задана!"));
a = aa; f = af; b = ab;
level = 1;
return Simpson
(a,b-a,f(a),4.0*f(0.5*(a+b)),f(b),1.0,1.0,Accuracy,OnStep);
}
/*В С# как и в C++ любой метод может быть перегружен.
В том числе и конструктор.
Обратите внимание, как из перегруженного конструктора без
параметров вызывается другой конструктор –
с двумя параметрами.*/
public TSimpson():this(DefNMax,DefAcc) { }
}
}
29
Download