Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Теперь снова вернёмся к нашему примеру «Точка на плоскости».






¨ Теперь мы знаем, что мы имеем возможность объявить несколько одноимённых методов, в частности несколько конструкторов в С++.

¨ Вопрос о конструкторе по умолчанию может быть решён несколькими способами:

§ Можно объявить конструктор без параметров, например:

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);

Зафиксируем и уточним синтаксис и семантику новых понятий, которые были задействованы в рассматриваемом примере.






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.