лекции (2004) (1160823), страница 15
Текст из файла (страница 15)
Самая главная - доступ к элементам класса.
Чаще всего это оператор точка: имя_класса.имя-члена
Члены бывают статическими и нестатическими. Еще в Smalltalk была переменная класса- которая соответствует статическому, и переменная экземпляра – нестатическому члену класса.
Статические существуют в одном экземпляре по отношению к классу:
class X
{
static int x;
int y;
...
}
Есть объекты y и остальные, и есть объект х, который общий для всех объектов типа X (он не размещается вместе с каждым объетом данного класса). Можно сказать, что х это такая глобальная переменная, доступ к которой возможен только через имя соответствующего класса. Во всех языках, которые мы рассматриваем (Cи#, Java), доступ к статическим член осуществляется не имя_объкта.имя_члена, а имя_класса.имя_члена. В языке Си++ доступ может быть двоякий: имя_объекта::имя_члена или имя_объекта.имя_члена, такая возможность – это недостаток языка, потому что к статическим членам есть смысл обращаться только через имя_класса. Поскольку статический объект не зависит от конкретного объекта класса, то он не зависит и от наличия объектов этого класса вообще, т.е. мы имеем право обращаться к статическим членам класса, даже если у нас не порождено ни одного объекта соответствующего класса.
Статическая функция – очень похожа глобальная, только вызов функции должен идти через имя соответствующего класса (локализация в пределах имени класса).
Заметим еще раз, что класс имеет дуальную природу: с одной стороны это тип данных, с другой класс начинает вести себя как модуль, особенно если говорить о статических функциях и статических членах. В языках Cи# и Java вообще отсутствует понятие глобальных переменных и функций, потому что есть классы и их статические члены.
Рассмотрим консольные приложения – что является главной программой или главным модулем? Является некоторый класс (обязательно публичный), в котором обязательно должна быть публичная статическая функция main, у которой аргументом является массив из строк argv. Ясно, что main будет существовать вне зависимости от существования объектов класса, и в языках Cи# и Java именно такая функция main является главной функцией любого консольного приложения
public class
public static main(String [] arg);
- для консольных приложений на языках Cи# и Java.
Нестатические данные – это то же самое, что и поле в записи в Паскаль или поле структуры в Си.
Нестатические функции-члены от статических отличаются тем, что им неявно всегда передаётся один параметр - ссылка на объект. При этом во всех объектно-ориентированных языках с понятием класса у нас есть возможность явно обращаться к этому параметру. В языках с объектно-ссылочной структурой эта самая ссылка представляет просто имя (зарезервированное) некоторого объекта. В языках Delphi и Java этот параметр называется self. В языке Си# это указатель this. В Си++ this имеет смысл не ссылки, а указателя. Очевидно, эту ссылку нельзя употреблять к статическим функциям, потому что у них нет этого параметра – вообще нет объекта. Как следствие - статические функции могут по определению работать только со статическими членами класса, либо в Си++ с глобальными объектами.
пример на языке Cи#:
class X
{
x, y
f(T1 x, T2 y)
{
this.x = x; // к this можно обращаться явно
this.y = y;
}
}
Для всех языков, которые мы рассматриваем употребление ссылки this не является обязательным. Если мы ее опускаем, то по умолчание имена локализуется в пределах этого класса (если речь идет о функции-члене). Класс является и областью видимости. Если у нас есть функция член данного класса (неважно статическая или нестатическая), аргументы данного класса попадают в область видимости или нет? Из примера (выше): локальные параметры процедур и функций образуют свою локальную область видимости, class X - образует свою области видимости, функция f(T1 x, T2 y) образует (локальными переменными и формальными параметрами) свою область видимости, вложенную внутрь Х. Как следствие, х и у из функции закрывают х и у из Х, однако к последним мы можем обратиться с помощью ссылки this.
В языке Дельфи такое не пройдёт, так как принято решение – формальные параметр процедур и функций образуют одну область видимости с членами этого класса. Мы должны избегать того чтобы формальные параметры назывались так же, как и члены класса. Если есть переменная item, то чаще всего программисты на Дельфи делают так:
procedure P(anItem: T).
В языке Cи# и Java такие вложенности запрещены:
{ int i;
{int i;
}
}
То же самое может распространяться и на некоторые локальные переменные соответствующих функций, но не на соответствующие параметры.
Языки программирования. Лекция 13.
На прошлой лекции мы рассматривали статические и нестатические члены.
Членами могут быть:
-данные;
-функции;
-другие ТД (например, классы, т.е. вложенные классы);
В языке Java есть статические и нестатические классы:
Статические классы:
Перед любым членом, в т.ч. и классом может стоять ключевое слово static.
class X{
static public class Y {...};
}
Статический класс тоже самое, что и вложенный класс в языке С. Областью видимости которого является объемлющий класс (класс Х). Доступ к классу Y можно осуществить только посредством класса Х (в зависимости от спецификации доступа приватный или нет).
Нестатический класс (отсутствие идентификатора static) может использоваться только внутри класса Х (похож на функцию-член нестатическую). Все функции-члены класса Y имеют доступ ко всем членам класса X.
В классе Х может быть несколько экземпляров нестатического класса:
Y y1; Y y2;
Членам нестатического класса передается ссылка на объемлющий член класса.
Однако такого рода конструкция не является необходимой - её можно промоделировать, добавив во вложенный класс ссылку на объемлющий.
Константные члены
В языке Java есть модификатор final - указывает, что значение не будет меняться.
final int i = 0;
Значение присваивается немедленно.
Если значение будет константным, то есть ли смысл делать его статическим?
В языке Си# пошли на следующее: все константы по определению являются статическими членами. Она инициализируется только один раз и может существовать вне зависимости от существования экземпляров объекта.
Но в Си# допускается модификатор real-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&);