Популярные услуги

Лекция 12

2021-03-09СтудИзба

Лекция № 12

Классы.

Инициализация/уничтожение объектов.

Тип данных вводятся через множество операций и соответствующую им структуру данных, т.е. множество значений  Если объект требует инициализации, а как правило даже тривиальная структура данных требует инициализации, прежде чем мы начнем с ней работать. Добавляют функцию типа:

T int (var X: T)

Если объект захватывает при своей инициализации какие-то ресурсы, то при уничтожении он обязан их освободить. Причем наиболее распространенный вид ресурса – это оперативная динамическая память. Если объект захватывает динамическую память, он должен ее освободить. Надо вовремя вызвать init и

Destroy (var X:T);

Рекомендуемые материалы

То есть

Init и Destroy полностью лежат на совести программиста.

Есть языки, где имеется динамическая сборка мусора. Например, Ада некоторые реализации, здесь динамическая сборка мусора лежит на разработчике. Оберон уже по определению имеет динамическую сборку мусора.

Динамическая память не является единственным ресурсом системы. У нас есть внешние устройства, файлы, которые , если мы открываем, то не должны забывать закрывать. Традиционные ЯП на поддержку инициализации и удаление объекта никак не реагируют, т.е. программист сам должен, если он объявил некоторую переменную данного типа, выполнить инициализацию и в случае, если необходимо соответствующее освобождение ресурсов, должен выполнить нужный destroy. Если он забыл это сделать, то только в отладочных средах есть соответствующие механизмы, позволяющие схватить программиста за руку. Но это только в динамических средах. В первые проблема такого рода роде решена в языке С++, где введены специальные функции, в частности разновидности этих функций: конструкторы и деструкторы. В чем специальность этих функций? Они  имеют так называемую системную семантику, т.е. компилятор об этих функциях знает несколько больше, чем о других функциях-членах классов. Компилятор автоматически вставляет вызов конструктора, когда объект размещается в память. Мы с Вами говорили, что в С++ есть три вида памяти – это статическая, квазистатическая (внутри блока) и динамическая.  

Т х;

Если статическая переменная х, то конструктор вызывается до начала работы программы, т.к. время жизни статической переменной равно времени выполнения программы. То есть появляется понятие стандартного пролога. Там происходит инициализация среды, инициализация некоторых базовых переменных процесса, инициализация стандартного канала ввода и вывода. Это все зашито в процесс компиляции, и пользователь иначе как пере компилируя программу с заменой объектного файла, который меняется от реализации к реализации, никак больше не может регулировать стандартный пролог и стандартный эпилог. Язык С++ к стандартному прологу языка С добавляет вызов статических конструкторов. Компилятор вставляет этот вызов автоматически, в этом и состоит специальность функций конструктора.

Конструктор имеет имя такое же как и класс.

 Class X {

          X(…)            конструктор, он ничего не возвращает.

};

Конструктор – это то, как говорил Страуструп, что превращает кусок памяти в настоящий объект. Деструктор вызывается при удалении объекта из памяти. Для статических объектов конструктор вызывается в стандартном прологе, а деструктор – в эпилоге. Для локальных объектов у нас конструктор вызывается, когда управление проходит через вызов данного объекта, ну и деструктор вызывается, когда мы выходим из блока. Блок можно покинуть двумя путями: либо через закрывающую скобку, либо, если этот блок составляет тело функции, через оператор return. Есть еще один способ, который будет обсуждаться позже, -- это выход, если в программе возникла аварийная ситуация.  Для объектов динамической памяти, когда вызывается new, вызывается еще и конструктор, а когда delete – еще и деструктор. Как правило, явным образом мы не вызываем конструктор и деструктор, хотя имеем полное право это сделать.

Class X {

     X(…)        описываем все необходимые  действия при инициализации      

   ~X(…)      описываем все необходимые действия при освобождение объекта

};

Таким образом в С++ реализована очень удобная концепция работы с ресурсами. Если ресурсы нам требуются временно, тогда мы просто описываем какой-то программный блок, в котором есть объект, имеющий конструктор, захватывающий нужный ресурс, и деструктор, его освобождающий. Наиболее характерный пример в данной ситуации – это выполнение длинной операции. Допустим, что мы заказали обмен с сетевым драйвером (эта операция в ряде случаев может достаточно на долго затянуться). Правила хорошего тона требуют от программиста как-то предупредить пользователя, что операция может затянуться, что бы последний не дергался. Чаще всего курсор превращается в песочные часы (интересно то, что им двигать достаточно не удобно – это тебе не стрелочка-указатель). Почти во всех графических библиотеках есть класс, в котором конструктор и деструктор реализованы как-то так:

CHourGlass( ) {

   m_old Cursor = Load Cursor :: CHourGlass; }

~ChourGlass( ) { LoadCurssor = m_odl Cursor };

Такой код работает очень хорошо. Правда от меня еще требуется проверки, а загрузился ли вообще курсор, но это уже превращение просто программы в хорошую программу.                                     
 

{ChourGlass  cur;

………………….     Выполняем какие-то действия

};

Даже если у нас произойдет какая- то беда и выход буден ненормальным,

курсор восстановится автоматически.

Но с другой стороны у нас возникают проблемы как при инициализации, так и при разрушении. Заметим, что в приведенном выше примере нам для инициализации не требовалось никакой дополнительной информации об объекте. Рассмотрим класс, задающий динамический массив. Здесь при инициализации хорошо бы было бы задавать длину массива.

Class Vector {

           Int * body;

                    Int size;

                   Vector (int S) { … }

        };

 Конструктор должен содержать параметр, определяющий соответствующую длину. Каким образом компилятор должен догадаться, какой параметр поставить? Есть понятие «конструктор по умолчанию», т.е. непараметрический конструктор (конструктор без параметров). Если нет явного указания, какой конструктор вызвать, компилятор вызывает конструктор умолчания.

Например, если написать что-то типа   х( );,  то будет поставлен конструктор умолчания, хотя в данном случае скобки лучше опустить и написать:  Т х;  Компилятор сам знает, что сюда вставить. В случае, когда у нас отсутствует конструктор умолчания, как  в примере  Vector (какое число тут можно взять по умолчанию? Никакое число не будет здесь лучше другого), следовательно, никакого конструктора умолчания у объектов данного класса быть не должно.  Мы должны иметь возможность явным образом указывать соответствующие параметры.

Vector v(10);

Vector *pv = new Vector(256);

Тут у нас уже никаких проблем нет, т.е. появляется несколько видов конструкторов: конструктор умолчания, конструктор с параметрами. Есть еще некоторая особенность языка С++, которая несколько усложняет работу с конструкторами, а, следовательно, и с объектами, поскольку каждый объект имеет конструктор. Иногда это удобно, но могут возникать проблемы из-за того, что конструктор в С++ имеет еще и некоторую системную семантику. Довольно часто можно увидеть конструктор, у которого собственное тело пустое. Это возникает от того, что кроме семантики, которую задает программист (пользовательской семантики), есть еще некоторая системная семантика, т.е. компилятор вставляет некоторые свои действия. Например, при наследовании. Пусть у нас есть иерархия классов: класс Т2 унаследован от класса Т1. При этом конструктор класса Т2 будет содержать в себе обращение к конструктору базового класса Т1. Сначала будет вызван при инициализации конструктор Т1, а потом по цепочки наследования будет вызван конструктор класса Т2.  Компилятор это делает автоматически, причем сверху вниз по цепочке наследования. Точно такая же ситуация и с деструктором, который в себе содержит некоторую системную часть. Сначала вызываются деструкторы производных классов, а по цепочке до базового. У конструктора сначала работа конструктора класса (здесь тело может быть пустое, т.е. пользовательская семантика), а потом конструктора объекта. У деструктора все в обратном порядке. Сначала вызывается деструктор пользовательского объекта, а потом вверх по цепочке наследования до базового класса. Отдельно рассмотреть надо случай, если конструктор содержит подобъекты. Любой класс в С++ содержит конструктор, хотя бы один, и содержит деструктор, тоже хотя бы один. Даже если мы не описываем его явно, код все равно будет сгенерирован автоматически, и тело будет непустое в случае, когда есть отношение наследования и отношение включения. 

Пусть есть класс Х:

Class X {

……..

};

Рассмотрим теперь класс Y, который содержит подобъект класса Х. Спрашивается, какой и когда конструктор будет вызван?

Class Y {

   X x;

};

Сначала вызывается конструктор базового класса, потом конструктор подобъектов, а уж потом конструктор объектов. Компилятор будет автоматически, когда он генерирует новый конструктор для класса Y, вызывать конструктор умолчания для подобъекта класса Х. А если конструктора умолчания нет, как в примере с классам Vector?  То в этом случае используется несколько расширенная семантика конструктора.

X (параметры) : конструкторы подобъектов.

Или

Y( ) : V(28) {…};                Здесь мы обязательно должны явным образом указать конструктор, потому что нет конструктора умолчания.

Все вышесказанное распространяется и на  базовый класс.

Для объекта типа ссылка такая возможность быть проинициализированной является единственно возможной, потому что мы говорили, что к ссылкам, вообще, применима только операция инициализации, а все остальные операции уже выполняется над тем объектом, на который ссылка указывает.

Class Y {

    int &i

};

Y( int a ) : i(a){…};

Из синтаксиса хорошо видно, что сначала происходит вызов конструктора базового класса, потом объекта, а потом уже пользовательская часть. Надо учитывать то, что если у нас много подобъектов, и мы соответствующие параметры перечислили чрез запятую, компилятор оставляет за собой право делать вызовы в такой последовательности, в которой ему удобно. Программист никогда не должен полагаться на то, что он знает в какой последовательности компилятор, что вызовет.

В языке С++ есть дополнительные навороты,  которые вызывают дополнительные трудности. Пока мы с Вами видели конструкторы умолчания и конструкторы параметрические, причем конструктор умолчания вызывается только тогда, когда нет вызова параметрического конструктора. Есть конструкторы явные, которые пишутся самим программистом, и есть конструкторы неявные, которые компилятор генерирует, если в классе явным образом не описан конструктор.

Есть еще одна проблема, которая уже связана с копированием.

Копирование объектов.

Поверхностное           Глубокое

                                                               (побитовое)                         (полное)

Есть две семантики копирования : поверхностное (побитовое) и глубокое (полное). Если объект содержит в себе ссылки на другие объекты, как в случае с объектом vector,


Ссылка на сам объект

Длина объекта

При поверхностном (побитовом ) копирование получится следующее:


  

Ри таком копированиии у нас появляются два объекта vector, но они ссылаются на одно и то же. Это влечет за собой проблему «висячей ссылки»

работы с динамической памятью.

Проблема возникнет очень остро, когда эти объекты начнут уничтожаться.  Если в конструкторе есть new, то в деструкторе обязательно должен быть delete. Значит первый деструктор очистит область памяти, на которую ссылается объект vector, а второму придется удалять, то что уже удалено до него. Это одна из очень неприятных ошибок, которую довольно трудно поймать в режиме проектирования и выполнения. Эту проблему должен осознавать сам проектировщик или программист. В языках с ссылочной семантикой (JAVA, C#, Delphi) таких проблем не возникает, потому что там идет просто копирование ссылок.  В С++ копирование должно иметь другую семантику. От сюда идет возможность перекрытия операции присваивания, т. е. можно писать

X& operator = (X&x) {…}


X a,b;

а=b                         вызывается

Операция присваивания, если она сгенерирована неявно, то она поверхностная (побитовая).

В языке С у нас можно было делать

(1)    int a, b;   обычное объявление переменных

Можно еще было написать так

(2)    int a=b;     

   

И программисты никакой существенной разницы не видели в том, чтобы написать как в первой строчке, а потом дописать

(3) а=b

или сразу написать одну вторую строчку.

Для простых типов данных никаких проблем нет. А если у нас

Т х=а;    (1)                                                      Т х;    (2)

                                                                         х=а;

 

                   здесь уже есть разница

Операция присваивания радикально отличается от конструктора тем, что она сначала должна освободить старый ресурс, а потом записать уже новый. В примере (2) сначала вызывается конструктор умолчания, потом операция присваивания тут же уничтожает то, что сделал конструктор, и делается копия. Это очень не эффективно. А вот для класса vector, вообще, отсутствует конструктор умолчания, так какой же конструктор будет вызываться при объявлении? Конструкция (1) в языке С++ носит особенную семантику. Там должен вызываться конструктор, но какой. Вызовется конструктор копирования.  Он имеет следующую семантику:

X ( X& ) {…}

Это похоже  на прототип объявления операции присваивания.

Или же

X (const x&) {…}

В языке С++ введено ограничение: если программист переопределяет операцию присваивания, он должен переопределять конструктор копирования и наоборот: если переопределяется конструктор копирования, то должна переопределяться и операция присваивания. В противном  случае компилятор выдаст ошибку. Конструктор копирования отличается от операции присваивания тем, что он проще, так как ему не нужно заботиться об уничтожении старого объекта. До инициализации объект не определен. Если нас не устраивает стандартное побитовое копирование, то нас не может устроить побитовое присваивание и наоборот. Исключение составляет лишь тот случай, когда мы не используем либо копирование, либо присваивание, для тех классов, для которых перекрыли либо конструктор копирования, либо оператор присваивания соответственно. Но этого избежать очень трудно, и чтобы не возникали подобного рода проблемы, компилятор сразу выдает ошибку. Если программист не определил ни конструктор копирования, ни операцию  присваивания, компилятор сам неявно сгенерирует и то, и другое.

На вскидку приходит то, что компилятор автоматически сгенерирует что-то типа memcpy(…); Но это не так. Ни один компилятор такого делать не будет. Почему? Вспомним семантику конструктора. Сначала вызывается конструктор для базового класса, а потом уже конструктор подобъектов. То же самое справедливо и для конструктора копирования. При копировании некоторые объекты могут быть уже проинициализированы. Семантика конструктора копирования отличается от семантики  простого побитового копирования, а именно, часть объектов может просто копировать побитовым образом, а для части – могут вызываться пользовательские конструкторы копирования.

Теперь понятно, что ни конструкторы умолчания, ни конструкторы копирования, ни операция присваивания не копируются. Если бы они наследовались, создавались бы дополнительные проблемы.

Есть еще третий специальный вид конструктора (первый – конструктор умолчания, второй – конструктор копирования) – конструктор преобразования.

Вопрос: нужны ли ЯП преобразования. С этой точки зрения ЯП делятся на две группы: ЯП, в которых запрещены преобразования (например, язык Оберон) и в которых преобразования разрешены. Там единственное, что разрешено, так это преобразование между простыми классами, причем преобразование от простого типа к сложному, т.е.


BYTE            INTEGER             LONG

такого рода преобразования разрешают выполнять все ЯП, это преобразования по отношению включения). Но ведь можно делать и более сложные преобразования. Например,  в язык С введена целая система неявных преобразований. Спрашивается, когда вводится новый тип данных, разрешено ли там делать неявные преобразования?  Например, в языке Ада существуют понятия типа и подтипа. Так вот неявные преобразования между типами и подтипами разрешены (они фактически относятся к одному типу), а между разными типами любые неявные преобразования запрещены (явные разрешаются).  Так вот возникает вопрос, разрешать или нет неявные преобразования в самом ЯП?  Так вот языки С++ и C#  разрешают неявные преобразования, причем они допускают, чтобы семантику этого преобразования описывал сам пользователь.

   Преобразования между данными все-таки нужны. Другое дело, что они всегда должны быть явными.

У нас есть в С++ тип данных char *, который служит для описания так называемых ASCI строк. Работа с string, т.е. массивом символов, есть одна их двух причин ненадежности языка С++ (вторая причина – работа с указателями). Одна из самых распространенных ошибок, связанная с ненадежностью сетевого обслуживания, представляет из себя переполнение буфера. Мы запрашиваем памяти меньше, чем реально потом заполняем. Эта проблема переполнения буфера всегда появляется при работе с динамической памятью. Есть специальные строки в разработанных библиотеках, которые позволяют работать со строками произвольной длины, т.е. там операция конкатенации совершенно безопасные. Хотелось бы иметь преобразование из типа char * в тип string.   Константы типа char * описываются через литералы.

Страуструп решил написать библиотеку для работы с комплексными числами (если посмотреть на область применения комплексных чисел, то мы увидим, что они перемешиваются с действительными), с точки зрения программирования это означает, что у нас одновременно работают типы данных int, float, double, complex и все это в одном ключе. Если бы у нас не было неявных преобразований, то для двуместной операции «+» надо было бы написать 16 вариантов операции «+». А подтипов еще больше (есть unsigned int, unsigned float и т. д.), если бы не было неявных преобразования, то даже простая библиотека разрослась бы до неимоверных пределов. Из тех языков, которые мы рассматриваем, С++ и С# разрешают неявные преобразования, а язык Java— нет.   Язык Java из проблемы преобразования типа данных string выходит очень просто. Там есть класс , который описан в пакете Java.long, а мы говорили, что обо всех объектах, которые там находятся, компилятор знает больше, чем обо всех остальных. А знает он следующее. Т.к. все классы выводятся их класса object, а там обязательно есть объект tostring( ) и его мы можем применить для любых объектов, это и есть преобразование в тип данных string. Даже если int, long… не являются классами, для них есть так называемые классы-обертки Int, Long, а вот в этих классах уже соответствующий метод есть.

Если мы пишем

string s;

s+5;       компилятор чувствует, что ему здесь нужно сделать преобразование, и он неявным образом вызывает метод tostring для класса Int, т.е. идет преобразование 5 к типу srting, а «+» расценивается как обычная операция конкатенации.

Если мы пишем

s+x, то это эквивалентно

s+x tostring();

Т. е. для большинства типов данных неявные преобразования  уже вставлены в сам язык, а других, пользовательских, неявных преобразований компилятор вставлять не разрешает. Это сделано для повешения надежности языка. В Delphi то же самое: там есть встроенный тип данных string, который ведет себя точно так же, как тип stirng в языке Java. Страуструп не хотел расширять базис языка. Он решил оставить реализацию таких типов данных, как string, complex на усмотрение разработчиков стандартных библиотек(так получилась библиотека шаблонов). Именно для того, чтобы облегчить жизнь именно этим разработчикам, он вынужден был ввести в язык неявные преобразования.  Как следствие у нас появился целый ряд проблем.

Х (Т);

Х (&Т);

Второй конструктор носит название «конструктор преобразования» и может вызывать не явно.

string (cons char *),  если есть такой конструктор, то имеем право написать

string s = (“abc”);

 Что в этом случае будет делать компилятор? В соответствии со стандартной семантикой разумно ожидать присваивания

string s(“abc); т. е.

string s = string (“abc”);

Точно так же, если у нас есть

string s;

(работает конструктор умолчания, который просто присвоит s пустую строку)

s = “abc”;

(компилятор сюда вставляет s = string (“abc”);)

Работает конструктор преобразования, а потом уже конструктор копирования или операция присваивания. Здесь вместо одного конструктора будут работать два. Аналогичная ситуация возникает, когда у нас есть просто класс Х и функция f, которая принимает объект типа Х по значению, а это означает, что х будет работа с копией х. Здесь будет работа не с операцией присваивания, а работать будет конструктор копирования, потому что при операции присваивания и левая, и правая части должны быть проинициализированы. А при копировании один их объектов не определен.

Копирование х происходит куда-то там в локальную память, следовательно работает именно конструктор копирования.

 

(1) void f (X x)


(2) X f( ) {X x; … return x;}

Здесь тоже х буде скопирован куда-то, а следовательно будет работать конструктор копирования, тук как операция return это есть копирование значения х куда-то.

Рассмотрим теперь случай, когда у нас есть конструктор преобразования.

int a;

  x = a;

  x = 5;   ~  x=X(5) (конструктор преобразования вставляется)

Тогда f(5);                корректный вызов

 

В вызове функции f (1) будет аналогично вставлен конструктор преобразования f(X(5)), который создает временный объект, а потом уже работает конструктор копирования.  Т.е. два конструктора на одном месте, когда достаточно одного. Компиляторы имеют право вставить следующую оптимизацию: т. к. самая важная здесь работа конструктора преобразования, то запись типа

string s = “abc”;

компилятор заменит на запись типа

strings(“abc”); 

тем самым будет сэкономлен один конструктор. Это есть некоторое усложнения языка.

Вернемся к нашему классу Vector, в котором нет конструктора умолчания, но можно описать конструктор преобразования. Если мы напишем что-то типа

int a;

Vector v;

v=a;      утверждается, что с вероятностью близкой к 1 эта запись ошибочна, т. е. программист случайно перепутал имена переменных. А на самом деле будет выполнено : V = Vector (a); и старое значение V будет потеряно. Эта ошибка, причем как любая динамическая ошибка, она то ли найдется, то ли нет. В таком случае программа, скорее всего, будет работать, но она станет выдавать разные данные, если у Вас возникает такая проблема, то у Вас явно есть не инициализированное значение. Эта возможная ошибка была замечена довольно рано, и Страуструп в 90-е годы (когда влияние С++ стало ничуть не меньше языка С, и как следствие Страуструп перестал бояться вводить новые слова) появляется ключевое слово explisit, которое ставится перед конструктором преобразования. Ключевое слово explisit означает, что конструктор может быть вызван только явно.

explisit Vector(int);

Vector v(10);         корректный вызов,

V = 5;  эквивалентно  Vector (5); это неявный вызов конструктора, следовательно, ошибка т.к. неявные вызовы запрещены. Хорошо бы было, если б ввели еще слово implisit, которое если мы хотим действительно неявный конструктор преобразования (как char *  к string), то мы его объявляем как  implisit.  Полное решение было бы ввести два слова: explisit и implisit, по умолчанию ставить implisit. Но сделали по-другому: ввели всего одно ключевое слово, и если не указано слово ехplisit, то данный конструктор может вызываться как по умолчанию, так и явно. Это было сделано исключительно из-за совместимости со старыми программами на С++, иначе пришлось бы все перекомпилировать, в том числе и стандартную библиотеку(естественно, что конструкторы преобразования встречались там очень часто).

С конструктором преобразования тесно связано понятие еще одной  специальной функции, а именно, «оператор преобразования» . Для нового класса конструктор дает способ преобразования в объекты нового класса их любого типа данных, в том числе и стандартных, встроенных и базис языка. Но тогда получается, из старых классов в новый можно делать преобразование, а из новых в старый нельзя. Получается некоторая несимметричность. Может случиться так, что мы не хотим расширять старый класс Т, добавляя в него конструктор преобразования  Т(Х). Может Т вообще не класс, а стандартный тип данных типа int или char *. Мы говорили, что есть преобразование string -> char *, и это хорошо : мы указываем литерал, и он у нас автоматически преобразовывается в строку, но обратное преобразование, char * -> string, тоже было бы полезно, причем неявное. Например, у нас есть операция cout в стандартной библиотеке, для которой перекрыта операция  “<<”, так вот, если есть неявное преобразование, то не надо делать перекрытие для char *. Появляется оператор преобразования, который может работать только, как функция-член класса, имеет только один параметр – указатель this. Например, для типа char * это будет выглядеть так:

Class X {

Operator char * ( ) {…};

}

Обычно в char * никто не преобразует, а все преобразуют в const char *.  

 

 Итак, у нас есть   конструкторы Х( ); – умолчания,

                                                      Х (Х&); – копирования,

                                                      Х(Т); – преобразования,

                                                     все остальные.

Правила, как мы видели, для соответствующих конструкторов не самые простые. Что с этой точки зрения делается в других ЯП, которые мы рассматриваем? Конструкторы есть и в Delphi, и в Java, и в C#. Синтаксис конструкторов в  Java и C# похожи. В Delphi нет зарезервированных имен, и мы должны пивать явно

Type T = class

    Constructor Create(…);

    Destructor Free;

Имя конструктора может быть любое, но как правило оно все-таки Create. Из-за совместимости с базовым типом object , из которого наследуются все классы, имеет конструктор Creatе. Деструкторы обычно без параметров, хотя параметризованные деструкторы разрешены как здесь, так и в С++.

Var X: T;

Это просто указание на то, что такой объект буден.

X:= T. Create(…);

Реально объект создается только после вызова конструктора. Когда вместе с конструктором вызывается менеджер динамической памяти, который отведет место под данный объект, а после уже его проинициализирует конструктор.

X.Free;

Деструктор вызывается всегда через ссылку на объект. Вместе с ним вызывается менеджер динамической памяти, который удалит память уничтоженного объекта.

Конструкторы и деструкторы не  генерируются самостоятельно, но зато они наследуются. Они не вызываются без явного указания на то. Т.е никто не будет вызывать за нас конструкторы базовых класса или конструктор подобъектов.

Это не очень важно, потому что объекты имеют ссылочную семантику, и их все равно надо инициализировать явно.

T=class

X:Y;       подобъект другого класса

               Место всегда отводится только под ссылку.

Y.Create;

Никакая системная семантика для инициализации конструктора в, общем-то, не нужна. Хорошо бы было если вызывались конструкторы базовых классов, но создатели решили, что это надо программисту, так пусть он явно и вызывает эти конструкторы. Вели специальное слово

Inherited Create;

Это если мы хотим вызвать метод из базового класса.

В С++ если у нас есть класс

Т1 и Т2 и мы хотим вызвать метод класса Т1, то пишем Т1::f( );

Если есть и функция Void g ( ) – член класса Т2, то правильно будет следующее.

T1    T2  

Void g ( ) {T1 :: f ( )}

Обратите внимания на то, что т. к. языки со ссылочной семантикой, то никаких вызовов по умолчанию ни конструктора, ни деструктора быть не может ни в Java, ни в C#, там надо проводить инициализацию явно.

X x = new X( );

В Java конструктор умолчания называется безаргументным конструктором. Безаргументный конструктор или конструктор умолчания автоматически вызывается для конструкторов базовых классов. Конструкторы подобъектов программист вставляет сам явно, потому что все они инициализируются с помощью операции new. В языке Java, если нам надо вызвать аргументный конструктор, вставляется ключевое слово super (…); 

Системная семантика в delphi полностью отсутствует, а в C# и Java – это самое много, вызов конструктора базового класса.  Конструкторы копирования не нужны. В Java отказались от преобразований, следовательно конструкторы преобразования там не нужны. В C# вместо того, что бы вводить конструктор преобразования и оператор преобразования, ввели просто оператор преобразования, который может быть статическим членом класса. Имеет эта функция один параметр:  что преобразовываем.

Рекомендуем посмотреть лекцию "Часть 14".

Static operator int (X x){…}

   Если есть два класса Х и Y, то мы можем задуматься, куда вставлять преобразование? Можно, куда больше нравиться.

В C#, как и в С++ есть неявные преобразования (преобразования, которые вставляет компилятор). Семантику этих неявных преобразований может задавать сам пользователь. Если есть стандартный класс Т1 и Т2, то нельзя делать оператор преобразования одного к другому( эти преобразования уже есть свои у компилятора). Наши базовые типы это не просто базовые типы языка С#, а базовые типы среды CLR или .NET. Правила преобразования из одного стандартного класса в другой одинаковые для языков C# и BASIC, и всех языков, входящих в среду CLR. Так как разрешено неявное преобразование, то может возникать проблема, описанная выше в С++, В C# есть ключевые слова explisit и implisit, по умолчанию вставляется implisit и, если стоит  explisit, это означает, что оператор преобразования вставляется только явно. Если есть explisit , и мы пишем

int a;
         X x;

a = x                  нельзя         

a = int (x);         верно

Свежие статьи
Популярно сейчас
Как Вы думаете, сколько людей до Вас делали точно такое же задание? 99% студентов выполняют точно такие же задания, как и их предшественники год назад. Найдите нужный учебный материал на СтудИзбе!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
5173
Авторов
на СтудИзбе
437
Средний доход
с одного платного файла
Обучение Подробнее