лекция 13 (Языки программирования (лекции) (2008))
Описание файла
Файл "лекция 13" внутри архива находится в папке "Языки программирования (лекции) (2008)". Документ из архива "Языки программирования (лекции) (2008)", который расположен в категории "". Всё это находится в предмете "языки программирования" из 7 семестр, которые можно найти в файловом архиве МГУ им. Ломоносова. Не смотря на прямую связь этого архива с МГУ им. Ломоносова, его также можно найти и в других разделах. .
Онлайн просмотр документа "лекция 13"
Текст из документа "лекция 13"
Языки программирования. Лекция 13.
На прошлой лекции мы рассматривали статические и нестатические члены.
Членами могут быть:
-данные;
-функции;
-другие ТД (например, классы, т.е. вложенные классы);
В языке Java есть статические и нестатические классы:
Статические классы:
Перед любым членом, в т.ч. и классом может стоять ключевое слово static.
class X{
static public class Y {...};
}
Статический класс – то же самое, что и вложенный класс в языке С. Областью видимости которого является объемлющий класс (класс Х). Доступ к классу Y можно осуществить только посредством класса Х (в зависимости от спецификации доступа приватный или нет).
Нестатический класс (отсутствие идентификатора static) может использоваться только внутри класса Х (похож на функцию-член нестатическую). Все функции-члены класса Y имеют доступ ко всем членам класса X.
В классе Х может быть несколько экземпляров нестатического класса:
Y y1; Y y2;
Членам нестатического класса передается ссылка на объемлющий член класса.
Однако такого рода конструкция не является необходимой - её можно промоделировать, добавив во вложенный класс ссылку на объемлющий.
Константные члены
В языке Java есть модификатор final - указывает, что значение не будет меняться.
final int i = 0;
Значение присваивается немедленно.
Если значение будет константным, то есть ли смысл делать его статическим?
В языке Си# пошли на следующее: все константы по определению являются статическими членами. Она инициализируется только один раз и может существовать вне зависимости от существования экземпляров объекта.
Но в Си# допускается модификатор read-only - они могут быть инициализированы в произвольных функциях-членах (не статическая инициализация), но присваивание допускается только один раз, изменять его уже нельзя.
Разница между константами и read-only-переменными - во времени связывания.
В С++ такого различия нет, константы этого языка ближе к read-only объектам.
константы бывают нестатическими (инициализируются в конструкторе и только в нем) и статическими:
class X {
static const int i; // По стандарту нельзя здесь инициализировать i!
}
Инициализировать его можно в соответствующем срр-файле:
int X::i = 0;
Т.к. она должна существовать независимо от наличия объектов данного класса.
Если мы забудем инициализацию, то при обращении к ней будет выдана ошибка связывания.
В остальных ЯП такого различия констант на const и reаd-only нет.
Специальные функции-члены
Во многих ЯП есть такие специальные функции-члены как конструкторы и деструкторы.
Ещё специальные функции-члены - это операторы (например, преобразования).
Компилятор про эти функции знает немного больше, чем об остальных.
Специальные функции-члены решают много проблем:
- Инициализация приложения;
- Уничтожение;
Проблема большинства языков без классов - программисты забывают вовремя вызывать процедуры инициализации и уничтожения, освобождение ресурсов. Например, стек, если стек в виде списка, то уничтожение необходимо, если в виде указателя на свободный элемент, то необязательно.
Впервые конструкторы и деструкторы предложены в Си++.
Ранее были вспомогательные ф-ии thunks, ф-ии-помошники, но вызов их зависел от программиста. Конструктор вызывается при размещении объекта в памяти, деструкторы при удалении из памяти.
class X{
X(...);// конструктор
~X();//деструктор, ~ введена для того, чтобы не вводить новое служебное слово, для совместимости с Си
...
}
Когда вызываются конструкторы статических объектов?
Очевидно, до начала работы программы.
Деструкторы статических объектов (если речь идёт о нормальном выходе) выполняются после завершения работы программы (стандартный пролог и эпилог).
Конструкторы квазистатических объектов выполняются при входе в блок. Деструкторы - при выходе из блока.
Для динамических объектов - при вызове new и delete соответственно.
1). Конструктор
В С++ (самый гибкий) любой конструктор имеет 2 составляющих:
1. Системная (стандартная);
2. Пользовательская (тело конструктора) - есть только у явных конструкторов (описанных программистами).
Неявные конструкторы - сгенерированные конструкторы.
Если конструктор неявный, то у него пустая пользовательская часть, а системная присутствует.
Может ли системная часть быть пустой?
struct Complex {
double Re, Im;
}
у этого класса системная составляющая может быть пустая, нет наследования и членов других классов.
В системной составляющей конструктора происходит
class Y public X{
Z z;
}
наследование: T--> T1-->....-->TN
- инициализация (вызов конструкторов) базовых классов (вызываются сверху вниз, т.е. от Т к ТN)
-
инициализация подчленов (объектов подклассов)( конструктор класса Z)
-
после этого выполняется пользовательское тело конструктора.
Аналогично и с деструкторами, но сначала выполняется пользовательская часть, а потом деструкторы подчленов, потом базовых классов( снизу вверх).
Классификация конструкторов:
-
Конструктор умолчания (конструктор без параметров с точностью до параметров по умолчанию: Х( int i=0)-->
X()-не конструктор умолчания и X(int)) - вызывается автоматически (Z z;); если у класса отсутствуют пользовательские конструкторы, то конструктор умолчания генерируется автоматически, в том и только в том случае, если у класса нет конструкторов вообще.
2. Конструктор копирования X(const X&).
int i = 1; // инициализация, а не присваивание. Вызов конструктора умолчания.
T *p;
p = new X(); // вызов конструктора умолчания, можно без скобок
T a(); // а это уже выглядит как прототип функции!
T a = b;// копирования
T(b);
a = b;// нет копирования, т.к. а инициализирован--> нет неинициализированных объектов, т.к. всегда есть конструктор по крайней мере умолчания.
Конструктор всегда работает только с неинициализированным объектом! Т. е. если у класса есть хоть один конструктор, то он не может быть не инициализирован!
void f(X x); // здесь так же вызывается конструктор копирования, передача параметров по значению!
X f(); // и здесь тоже, возвращает объект типа Х - return
Явный конструктор копирования стоит описывать только, когда нам не хватает семантики. Если нет конструктора копирования, то он генерируется по умолчанию - но он не только побитово копирует один класс в другой, у него есть базовая семантика(для членов простых ТД производится побитовое копирование, а для членов-классов - соответствующий конструктор копирования класса).
Если надо будет, то мы можем запретить использовать конструктор копирования (объявить его в private-секции).
Явный конструктор копирования следует описывать тогда, когда побитовой семантики копирования бывает недостаточно (вложенные ссылки, классы и т.д.).
Есть два типа копирования:
1. Поверхностное копирование (Shallow) - при копировании ссылки на другие объекты просто копируются;
2. Глубокое копирование (Deep) - при копировании другие объекты (на которые ссылается копируемый объект) тоже копируются.
Если мы сами переопределяем конструктор копирования, то мы должны переопределить оператор присваивания и наоборот. Присваивание работает с уже созданными объектами, и ресурсы, которые мы затираем, их надо освобождать.
Пример(разница между копированием и присваиванием ):
проблема глубокой копии
char String {
char *body;
String(String&);
String &operator = (String&);
};
String(String &S)
{
body = new char [S.Length() + 1];
strcpy(body, S.body);
}
String &operator = (String& S)
{
delete [] body;
body = new char [S.Length() + 1];
strcpy(body, S.body);
}
// если копировать строку саму в себя, то будет ошибка
3. Конструктор преобразования
X(T)
X(T&)
X(const T&)
Т отличен от Х!
В языке Си++ допустимы неявные преобразования (это преобразования, которые вставляются не программистом, а компилятором). Стоит заметить, что неявные преобразования - довольно опасная вещь.
class Vector {
....
Vector (int size);//конструктор, кол-во объектов в данном классе
T& operator [] (int i);//оператор индексирования
}
Vector V(20);
Vector X(10);
похоже на:
T V[20];
T X[10];
но в отличие от обычных массивов :
V = X;
V = 3; // ошибка, но не с точки зрения компилятора - он считает, что это V = Vector(3), отыскивая пользовательские конструкторы копирования;
Для этих случаев в Си++ создан модификатор explicit - невозможность использования неявных преобразований.
В языке Java разрешено всего два неявных преобразования: преобразование к типу данных Object и вызов метода ToString.
Нет неявных преобразований в Delphi, Ada.
Возникает вопрос: почему в языке Си++ неявные преобразования очень сильно распространены?
Рассмотрим пример вычисления выражения:
A = B + C * exp(I*X);
На языке Си без неявных преобразований:
A = Plus(B, Mult(C, EXP(Mult(I,X)))).
При добавлении комплексного типа данных, проблема при перегрузке операторов: большое кол-во вариантов, из-за возможных комбинаций типов.
При использовании неявных преобразований все становится проще: 11 конструкторов преобразования и варианты ф-ий.
Си# неявные преобразования разрешены.
4. Все остальные конструкторы (никакой особой семантики нет).
2).Деструктор
Деструкторы бывают двух видов:
1. Деструктор умолчания;
2. Пользовательский деструктор.
Посмотрим, как дела обстоят в остальных языках.
Cи++ все конструкторы.
В языке Cи# и Java есть конструктор умолчания. А вот конструктора копирования у них нет! Потому что у них нет побитовой семантики по определению - вместо объектов там все операции идут с указателями на объекты (a = b) - ссылочная семантика.
А для копирования есть метод Object Clone()- возвращает ссылку на объект. Для копирования- переопределить Object Clone().
Конструкторы преобразования в Java и Delphi отсутствуют, в Cи# преобразование делается исключительно при помощи операторов преобразования.
В силу ссылочной семантики конструктор вызывается явно.
Си# - base(...);
Java - Super(); от smalltalk
Во всех этих языках есть инициализаторы при членах (T x = e), выполняется она сверху вниз(в порядке, в котором они расположены в тексте).
static {...блок с любыми присваиваниями статическим членам класса вызывается, когда инициализируется первый статический член данного класса}; - статический инициализатор.
Если в языке Cи# есть у конструктора static, то это статический конструктор и он аналогичен статическому инициализатору в языке Java . Этот статический конструктор - конструктор умолчания .Т.к. инициализация явная и никаких параметров нет.
Для класса vector конструктор умолчания нецелесообразен, т.к. нет кол-ва элементов по умолчанию.
Если кто-то берёт и унаследует класс Vector, создавая класс X (c переменной int &k ). Должна быть специальная конструкция для вызова конструкторов баз и подчленов (Vector(20), k(i)).
В Си++ следующая семантика:
конструктор:
X(int i): Vector(20), k(i) {
k = i;// операция присваивания--> нужна явная инициализация
}
...
Си# - base(...);- в Си нельзя, т.к. баз может быть много.