Главная страница
Случайная страница
КАТЕГОРИИ:
АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника
|
Теперь снова вернёмся к нашему примеру «Точка на плоскости».
¨ Теперь мы знаем, что мы имеем возможность объявить несколько одноимённых методов, в частности несколько конструкторов в С++.
¨ Вопрос о конструкторе по умолчанию может быть решён несколькими способами:
§ Можно объявить конструктор без параметров, например:
TCPoint();
§ Можно в одном из конструкторов инициализировать все формальные параметры, например:
TCPoint/*GenXY*/(double prmX=0, double prmY=0);
Тогда этот конструктор будет (по совместительству) конструктором по умолчанию, т.к. его можно вызывать с пустым списком параметров.
Но(!!!) надо помнить – конструктор по умолчанию может быть только один (иначе возникнет двусмысленность, какой из них вызывать...).
¨ Вопрос о конструкторе GenRoFi к сожалению останется:
§ TCPoint/*GenRoFi*/(double prmRo, double prmFi);
Такое объявление недопустимо – такая перегрузка выше объявленного TCPoint/*GenXY*/ нарушает требования о несовместимости.
§ TCPoint/*GenRoFi*/(float prmRo, float prmFi);
Такая перегрузка приемлема, но представляется не очень естественной, к тому же потребует использования приёмов явного приведения типа double ® float ® double
Можно подумать ещё о каких-нибудь подходящих способах обхода этой ситуации.
Object Pascal 2
| C (C++)
| Ø Генераторы объектов
¨ Конструкторы
| CONSTRUCTOR GenXY(
prmX, prmY: REAL);
CONSTRUCTOR GenRoFi(
prmRo, prmFi: REAL);
| TCPoint/*GenXY*/(
double prmX=0,
double prmY=0);
//Пусть будет так:
void setRoFi/*GenRoFi*/(
double prmRo,
double prmFi);
// т.е. модификатор,
// а не конструктор.
| ¨ Генераторы в более широком смысле
§ Создать новую точку в положении после поворота базовой точки вокруг центра системы координат
§ Создать новую точку аналогично, но параллельным сдвигом, который задан радиус-вектором другой точки
| FUNCTION GenRotat(
prmDFi: REAL): TCPoint;
FUNCTION GenSum(
prmP: TCPoint): TCPoint;
END{класса};
| TCPoint GenRotat(
double prmDFi);
TCPoint GenSum(
TCPoint prmP);
}/*класса*/;
| implementation
FUNCTION TCPoint.GetX: REAL;
BEGIN GetX: =x END;
//... GetY... аналогично
FUNCTION TCPoint.GetRo
: REAL; BEGIN GetRo: =
SQRT(x*x+y*y) END;
//... GetFi...
FUNCTION TCPoint.GetEq(
prmP: TCPoint): BOOLEAN;
BEGIN GetEq: =(x=prmP.x)AND
(y=prmP.y) END;
PROCEDURE TCPoint.SetX(
prmVal: REAL);
BEGIN x: =prmVal END;
//... SetY... SetRo...
PROCEDURE TCPoint.SetFi(
prmVal: REAL); VAR r: REAL;
BEGIN r: =SQRT(x*x+y*y);
x: =r*COS(prmVal);
y: =r*SIN(prmVal) END;
PROCEDURE TCPoint.Rotat(
prmDFi: REAL); BEGIN
SetFi(GetFi+prmDFi) END;
//... Sum...
CONSTRUCTOR TCPoint.GenXY(
prmX, prmY: REAL);
BEGIN x: =prmX; y: =prmY END;
CONSTRUCTOR TCPoint.GenRoFi
(prmRo, prmFi: REAL);
BEGIN x: =0; y: =0;
SetRo(prmRo); SetFi(prmFi)
END;
//... GenRotat...
FUNCTION TCPoint.GenSum(
prmP: TCPoint): TCPoint;
BEGIN GenSum: =TCPoint.
GenXY(x+prmP.x, y+prmP.y)
END;
END.
| //Файл unit1.cpp:
#include " unit1.h"
double TCPoint:: GetX()
{return x; }
//... GetY... аналогично
double TCPoint:: GetRo()
{return sqrt(x*x+y*y); }
//... GetFi...
bool TCPoint:: GetEq(
TCPoint prmP)
{return (x==prmP.x)& &
(y==prmP.y); }
void TCPoint:: SetX(
double prmVal)
{x=prmVal; }
//... SetY... SetRo...
void TCPoint:: SetFi(
double prmVal){double r;
r=sqrt(x*x+y*y);
x=r*cos(prmVal);
y=r*sin(prmVal); }
void TCPoint:: Rotat(
double prmDFi){
SetFi(GetFi()+prmDFi); }
//... Sum...
TCPoint:: TCPoint/* GenXY*/(
double prmX, double prmY)
{x=prmX; y=prmY; }
void TCPoint:: setRoFi
/*GenRoFi*/(double prmRo,
double prmFi)
{x=0; y=0;
SetRo(prmRo); SetFi(prmFi); }
//... GenRotat...
TCPoint TCPoint:: GenSum(
TCPoint prmP)
{return TCPoint(
x+prmP.x, y+prmP.y);
}
| Замечания к С++ программе. Рассмотрим немного иную реализацию метода GenSum:
¨ TCPoint TCPoint:: GenSum(TCPoint prmP){TCPoint u;
u.x=x+prmP.x; u.y=y+prmP.y; return u; }
- В этой реализации сначала (неявным вызовом конструктора по умолчанию) создается локальный объект u, потом он модифицируется и возвращается как значение функции.
- Вызывает беспокойство, что возвращаемое функцией значение является локальным объектом, но это допустимо - семантикой вызова функции предусмотрено копирование возвращаемого значения. Вот если бы функция возвращала указатель на локальную переменную, то возникли бы проблемы...
Фактически в этой реализации явно прописано ровно то, что в предыдущей реализации предписывает семантика явного вызова конструктора в С++.
¨ TCPoint TCPoint:: GenSum(TCPoint prmP){
TCPoint u=TCPoint(x+prmP.x, y+prmP.y); return u; }
В этой реализации использована инициализация переменной типа класс явным вызовом конструктора в С++. Отметим, что в Object Pascal 2 такой возможности нет.
¨ TCPoint TCPoint:: GenSum(TCPoint prmP){
TCPoint u(x+prmP.x, y+prmP.y); return u; }
Эта реализация просто иллюстрирует, что в предыдущем случае можно было использовать сокращенную запись инициализации вызовом конструктора.
Object Pascal 2
| C (C++)
| program Project1;
uses Unit1;
TYPE TPoint=
RECORD x, y: REAL END;
VAR p0: TPoint;
p1, p2, p3: TCPoint; BEGIN
p0.x: =1.5; p0.y: =2*p0.x;
| //Файл prgoopC.cpp:
#include " unit1.h"
main(){typedef
struct{double x, y; }TPoint;
TPoint p0;
TCPoint p1, p2, p3;
p0.x=1.5; p0.y=2*p0.x;
| Тип TPoint не препятствует стандартному (прямому) доступу к компонентам.
| // p1.x: =1.5; p1.y: =2*p1.x;
| // p1.x=1.5; p1.y=2*p1.x;
| Но при попытке аналогичной работы с переменной типа TCPoint получаем сообщение периода трансляции:
Undeclared identifier: 'x'
private-компонент x объекта p1 – невидим вне методов класса.
| // p1.SetX(1.5);
// p1.SetY(2*p1.GetX);
|
| Попытка воспользоваться public-методами класса тоже оказывается неудачной – сообщение периода выполнения:
access violation...
(нарушение прав доступа)
|
В Object Pascal 2 описание переменной типа класс не приводит к её созданию, необходим явный вызов конструктора:
|
| p1: =TCPoint.Create;
p1.SetX(1.5);
p1.SetY(2*p1.GetX);
|
p1.SetX(1.5); p1.SetY(2*p1.GetX());
| Откуда взялся конструктор Create? В Object Pascal 2, если не объявлено, чьим наследником является класс, то считается, что он наследник предопределенного класса TObject и наследует в частности конструктор Create своего родителя.
| В С++ явный вызов конструктора не понадобился, так как конструктор (неявно) отработал уже в объявлении объекта p1.
Отметим, что в С++ конструкторы не наследуются.
| p2: =p1;
WRITELN(p2.GetX, p2.GetY);
p1.SetX(-1); p2.SetY(-2);
WRITELN(p1.GetX, p1.GetY,
p2.GetX, p2.GetY);
| p2=p1;
cout< < p2.GetX()< < p2.GetY();
p1.SetX(-1); p2.SetY(-2);
cout< < p1.GetX()< < p1.GetY()
< < p2.GetX()< < p2.GetY();
| § Объект p2 не создан, но первое присваивание нормально отрабатывает.
§ Последующие присваивания, как покажет выполнение программы, дают «странный» результат: p2, p1 получили новое значение, но оно одинаковое.
§ Если объект p2 создать до оператора p2: =p1, то ситуация не изменится.
§ В Object Pascal 2 для обектов действует не традиционная семантика оператора присваивания (семантика присваивания по значению), а иная – семантика присваивания по ссылке. Согласно этой семантике p2 - не новое хранилище данных, а второе имя хранилища с именем p1.
| В С++ в отличии от Object Pascal 2:
§ Объект p2 был создан ещё при объявлении.
§ Последующие присваивания, дают ожидаемый результат: p2, p1 получили новое значение, оно разное и соответствует выполненным присваиваниям.
§ В С++ для обектов действует традиционная семантика оператора присваивания - семантика присваивания по значению.
| p2: =TCPoint.GenXY(1, 2);
WRITELN(p1.GetX, p1.GetY,
p2.GetX, p2.GetY);
| p2=TCPoint/*GenXY*/(1, 2);
cout< < p1.GetX()< < p1.GetY()
< < p2.GetX()< < p2.GetY();
| § Объект p2 пересоздан явным вызовом конструктора, это новый объект и его создание, как и ожидалось, не оказало никакого влияния на объект p1.
§ Но возникает вопрос, а что теперь с хранилищем данных, обозначением которого раньше служило имя p2:
- В программе С++ это хранилище данных потеряло обозначение, и теперь оно недоступно, это «мусор» - место в памяти занимает, но использовано быть не может.
§ В программе Object Pascal 2 у этого хранилища осталось ещё второе обозначение p1, но если бы его не было, то получили бы ровно ту же ситуацию.
| p3: =p2.GenXY(3, 4);
WRITELN(p3.GetX, p3.GetY,
p2.GetX, p2.GetY);
p2.SetX(-3); p3.SetY(-4);
WRITELN(p3.GetX, p3.GetY,
p2.GetX, p2.GetY);
| //p3=p2.TCPoint(3, 4);
| § p3 получил значение вызовом конструктора как метода объекта p2, а не тем способом, который был ранее рассмотрен и назван как базовый вариант.
§ Но как показывает последующее, мы опять имеем эффект двух имен.
§ Вызов конструктора как метода объекта, допустим в Object Pascal 2, но имеет другую семантику: при таком вызове не создается новый объект, а только выполняется тело конструктора, применяется оно к тому объекту, для которого он был вызван (как его метод).
Присваивание p3: =p2... в итоге имеет такой же смысл, как и ранее рассмотренный случай (p2: =p1): сначала было изменено значение существующего объекта p2, а потом, согласно семантике присваивания по ссылке, p3 стало дополнительным именем объекта p2.
| § Попытка явного вызова конструктора как метода объекта:
p3=p2.TCPoint(3, 4);
даст сообщение:
function-style cast': illegal as right side of '.' operator
в С++ нельзя вызывать конструктор как метод объекта.
| p2: =TCPoint.
GenRoFi(1, PI/6);
p3: =p1.GenSum(p2);
| p2.setRoFi(1, pi/6);
p3=p1.GenSum(p2);
/* Примеры объявлений объектов с инициализацей вызовом конструктора: */
TCPoint p4=TCPoint(1, 2); TCPoint p5(1, 2);
TCPoint p6(1);
| Зафиксируем и уточним синтаксис и семантику новых понятий, которые были задействованы в рассматриваемом примере.
|