лекции (2011) (1160854), страница 6
Текст из файла (страница 6)
Функции-члены, обладающие семантикой обычных функций-членов, о которых компилятор имеет дополнительную информацию.
Конструктор – порождение, инициализация
Деструктор – уничтожение (В Java и C# не деструкторов, вместо это можно сделать собственный метод Destroy() )
У конструктора нет возвращаемого значения.
Т.к. все объекты в C# и Java и Delphi размещаются в динамической памяти, то в этих языках обязательна операция явного размещения объектов:
X = new X(); // В Си++ X * a = new X;
Синтаксис конструкторов и деструкторов:
C++. C#, Java
class X
{
X(«параметры»);// В С# и Java обязательно определение тела
}
Delphi
type X = class
constructor Create; // Имя конструктора произвольное
destructor Destroy; // имя деструктора произвольное
end;
…..
a:X;
….
a := X.Create;
В C++, C#, Java конструкторы не наследуются, но могут автоматически генерироваться компилятором по определённым правилам.
Классификация конструкторов:
1.Конструктор умолчания X();
2.Конструктор копирования X(X &); X(const X & );
3.Конструктор преобразования X(T); X(T &); X(const T &);
В классах из простанства имён System платформы .NetFramework не определены конструкторы копирования. В С++ автоматически могут генерироваться конструктор умолчания и конструктор копирования
Вместо этого, если это предусмотрено проектировщиками, имеется метод clone();
Java, C#, D – в базовом типе object есть конструктор Create и деструктор Destroy. Соответственно здесь ничего создавать не надо, они уже есть и наследуются (если).
Конструктор умолчания
X a; - подразумевается вызов конструктора по умолчанию (неявно).
X a(); - нельзя, т.к. это прототип функции.
X* px = new X; - нельзя в Java и С#, в С++ - можно.
X* px = new X(); //В C# и Java можно только так
Java, C#: Понятие КУ остается.
Есть понятие инициализация объектов:
class X
{
Z z = new Z(); // Z z; - значение неопределенно.
Int I = 0; //простые инициализации можно выполнять непосредственно в коде самого класса
}
Вызов конструктора базового класса в Java может быть только первым оператором тела конструктора. Если первый оператор отличен от вызова super, то компилятор автоматически вставляет super();//вызов конструктора умолчания базового класса.
Пример на Java:
class A
{
public A(int I) { … }
...
}
class B extends A
{
public B() { super(0); … }
...
}
Пример на C#:
class A
{
public A(int I) { … }
...
}
class B : A
{
public B() : base(0) { … }
...
}
Существует статический конструктор, который вызывается 1 раз до первого использования и до первого обращения к любым членам класса. (С#)
static X() {…………}; //полная форма статического конструктора по умолчанию в языке C#
Java
static{…………….}; //аналог статического конструктора в Jav
M-2, Ада:
Init(); – явная инициализация.
Destroy();
Для определения собственного конструктора и деструктора достаточно было переопределить эти функции.
Конструктор копирования
С++
void f(X x); // передача фактического параметра функции по значению
X f() {return X();}
X a = b; //Вызов КК. ~ X a(b); «Инициализация присваиванием»
(1) X(X&);
(2) operator=(X&)
Если мы не копируем объект, то конструкции (1) и (2) не нужны. Описав прототип этих функций, но не определив их, мы запрещаем копировать объект данного класса. Когда отсутствует и прототип, и объявление, конструктор копирования генерируется автоматически.
С#: object.
В классе Object(общего предка для всех классов), есть защищенный метод MemberwiseClone, возвращающий копию объекта.
По умолчанию копировать нельзя, но в произвольном классе можно самим переопределить.
ICLoneable Clone();
Java:
В этой точки зрения наиболее адекватно проблема решена в Javа. Там существует 4 уровня поддержки копирования.
Интерфейс-маркер – по определению пустой интерфейс(не содержит членов).
Интерфейс – это просто набор методов. Он определяет некий контракт, говорящий о том, что если класс поддерживает некий интерфейс, он должен реализовывать определенный набор методов. А если интерфейс пустой, то все его члены-методы «зашиты» в компилятор.
Интерфейс называется сloneable, когда он пустой.
В Java был введен пустой интерфейс cloneable, содержащий метод Clone(), осуществляющий побитовое поверхностное копирование.
protected object Clone();
Уровни поддержек:
-
Полная поддержка копирования – возможность явной реализации. Класс X реализует интерфейс Cloneable:
Сlass X: Cloneable{
//Тут мы должны написать:
public X Clone();
//Допускается также:
public Object Clone();
Метод Clone() может использовать любые члены класса(и приватные тоже.)
};
-
Возможна и другая ситуация: полный запрет копированияя: при попытке скопировать объект выбрасываем исключение. Подменяем соответсвующий защищенный метод clone():
class X{
protected Object Clone(){ throw CloneNotSupportedException; }
………………………………….
};
-
Условная поддержка: элементы, которые копируются, могут быть под полным запретом.
Пример: коллекция умеет себя копировать, а элементы, из которых она состоит – нет.
class X: Cloneable{
public X Clone throwing CloneNotSupportedException
{
//Для каждого элемента коллекции вызывается метод Clone();
};
…………………………………………………..
};
-
Еще одна ситуация – когда мы не наследуем метод Clone()
Метод копирования нельзя переопределять.
D: inherited Create; //inherited – вызов соответствующего конструктора.
Деструктор – функция, которая вызывается автоматически, когда уничтожается объект.
С++, D, C# - ОО модель.
C++
delete p;
Отличие С++ от Delphi – в нем происходит автоматический вызов деструктора.
Общая проблема - в процессе функционирования объекты получают некий ресурс.
В С++ и Delphi мы всегда контролируем, когда ресурсы освобождаются.
Специальные функции в Delphi:
X:=I.Create();
X.Create();
X.Free
C#, Java – Автоматическая сборка мусора.
При динамической сборке мусора сложно определить, когда объект больше не используется.
Image.FromFile(fname);
…
Image.SaveToFile(fname);
Освободить файл можно, когда мы указываем, что с ним не буде работать.
Т.е. сборка мусора здесь не поможет.
=> C#, Java:
try
{
…
} finally {…}
D:
try
{
…
} finally
…
End
Такая вещь, как finally, очень важна. Она будет выполнена независимо от того, как кончился блок.(Это необходимо, так как в C# и в Delphi нету вызова конструктора по умолчанию в конце блока)
IDispose - общий интерфейс освобождения памяти. (C#) Данный метод вызывает финализатор объекта и ставит его в очередь на уничтожение, обеспечивая выполнение деструктора
Dispose();
try { im = … } finally {im.Dispose;}
Вводится специальная конструкция:
using(инициализатор) //инициализатор – T x=expr; x=expr;
блок
~
try {инициализатор} finally {x.Dispose;}
С#, Java: object
Учитывая то, что в Java есть сборщик мусора, там не существует деструктора. В классе Object существует защищенный метод:
protected void finalize();
public void Close();
Есть методики, которые позволяют возродить уничтоженный объект. Но finalize – полностью его удаляет (нельзя вызывать дважды).
В случае, если класс на протяжении своего существования должен освобождать ресурсы не один раз, он обязан содержать метод Close(), который будет это делать. Метод Dispose() вызывается один раз , а close должен быть запрограммирован таким образом, чтобы можно было вызывать его много раз.
Close() – ресурсы освобождены.
В Java метод finalize() вызывается сборщиком мусора. В C# существует деструктор – тонкая обертка для финализзатора finalize().
C#: ~X(){…} – нельзя вызывать явно.
System.Object.finalize – можно вызвать явно.
Сбощик мусора:
mark_and_sweep
Живые и мертвые объекты (ссылки).
Есть стек, в нем ссылки на объекты. Если живой, то помечаем и заносим в таблицу живых объектов, остальные – мертвые, они-то и уничтожаются.
Можно построить КЭШ объектов. Если объект мертвый, то нм нужен он. Но он еще не утилизирован (не успели).
Strong reference – на живой объект.
Weak reference – объект готовится к уничтожение, не пока еще не нуничтожен.
Преобразование типов и классы. Явные и неявные преобразования.
Управление преобразованиями в современных ЯП: проблемы и способы их
решения.
Неявные (автоматически вставленные компилятором).
Int long
А может ли их задавать пользователь.
В Java и Delphi нет возможности описания пользователем неявных преобразований (в Java нельзя перегружать стандартные операторы).
С++, C# – неявные преобразования, задаваемые пользователем разрешены.
Ф: v = expr – можно считывать различные типы данных.
C#:
«Неплоский» класс – это класс, который сожержит в себе ссылки на другие объекты.
Class Vector
{
T* body;
int size;
public:
Vector(int sz) {body = new[size = sz];}
}
Vector v(20); //ok
Vector t(10);
v = t;
v =1; ~ v = Vector(1); // ошибки не будет: сработате оператор преобразования: у нас «случайно» получился конструктор преобразования.
Решение: если мы не хотим, чтобы наш конструктор «использовался» в подобных целях, то с помощью ключевого слова explicit конструктор может стать обычным конструктором. Вот так:
explicit vector(int sj){Body = new T[size=sj];}
Теперь наш конструктор может вызываться только явноV = Vector(1);…
В C# explicit принято по умолчанию. Существует в C# и ключевое слово implicit.
Cвойства (properties).
«Свойство» - это член класса, который с точки зрения обращения к нему выглядит как член-данное, но с точки зрения реализации представлен двумя методами, один из которых возвращает значение свойства, а второй — устанавливает его значение. При этом один из методов может отсутствовать, делая недоступной соответствующую операцию над свойством.
В С++ нет. Все данные по определению закрытые, а вместо операций для свойста есть геттеры и сеттеры.
Есть в Delphi, C#.
Пример для языка Delphi – целое свойство Prop:
type PropSample = class
…
private
procedure SetPropVal(V : integer);
function GetPropVal:integer;
public
property Prop: integer read SetPropVal write GetPropVal;
…
end;
published//все опубликованные свойства появляются в интегрированной среде разработки.
property X: T read_x; write_x;
С#:
class X
{
T prop {get{…} set{…}} //value является зарезервированным в set.
}
…
X a = new X();
a.prop = t1;
T t2 = a.prop;
Классы и перегрузка имен. Перегрузка встроенных знаков операций.
В Java нет перегрузки.
С++: T operator+(T x1, T x2);
В Си++ typedef задаёт синоним типа, а не создаёт новый тип.
typedef int a;
void f(a x)
void f(int x) Перегрузки не будет, т.к. «a» не новый тип(=> будет ошибка)
Ада: function “+” (x1, x2: T) return T;
Перегружаемый операторы в C#:
-
базисный
-
арифметический
-
побитовый
[ ] в C# перегружать нельзя(в отличие от С++, в которых Страуструп решил проблему в общем случае). Общий синтаксис:
T operator *(операнд) {…………………}