лекция 12 (Языки программирования (лекции) (2008)), страница 2
Описание файла
Файл "лекция 12" внутри архива находится в папке "Языки программирования (лекции) (2008)". Документ из архива "Языки программирования (лекции) (2008)", который расположен в категории "". Всё это находится в предмете "языки программирования" из 7 семестр, которые можно найти в файловом архиве МГУ им. Ломоносова. Не смотря на прямую связь этого архива с МГУ им. Ломоносова, его также можно найти и в других разделах. .
Онлайн просмотр документа "лекция 12"
Текст 2 страницы из документа "лекция 12"
package P
P1
M P P.P1 ... Pn
M1
D
D1
end P;
P.P1 //все имена описанные в спецификации соответствующего пакета, они становятся потенциально видимыми в точке после
Непосредственно видимыми являются только имена соответствующих пакетов, все остальные имена являются потенциально видимыми. Если для Оберона этого было достаточно, то в Аде этого совсем не достаточно, так как в Аде допускается перекрытие операций, а именно перекрытие имен процедур и функций, при этом это перекрытие распространяется и на стандартные знаки операций.
package Vectors is
type Vector is ...;
function "+" (V1, V2: Vector) return Vector; // переопределение «+»
Утверждается, что если у нас есть только потенциальная видимость, то смысл в использовании этого перекрытого оператора «+» просто пропадает.
X1, X2, X3:Vector;
X3:= Vectors."+" (X1, X2); //Вся прелесть оператора "+" теряется.
Для этого есть конструкция, которая снимает потенциальную видимость, делая имена непосредственно видимыми:
use список_имён_пакетов;
В языках Модула-2 и Дельфи (с простой модульной структурой) import (uses) должны быть первыми операторами. В языке Ада модули могут быть вложены друг в друга, и от их порядка очень многое зависит. И предложение use может быть на любом уровне.
Пример:
package М
package М1
N
end М1;
1.//М1.N – видимо потенциально в данной точке
2. если
use M1
package М2
N
end М2;
1.//М1. N и М2. N – видимо потенциально в данной точке
2. то видно N и М2. N
3. use M2 – видны имена и из М1 и из М2, и они между собой конфликтуют и имя N совсем не видно – в результате семантика программы может сильно поменяться
end М;
Итак, от добавления или перемещения use – семантика программы может весьма измениться. Во многих организациях использование оператора use было "законодательно" запрещено. Вместо него использовался другой:
имя1 renames имя2 // исправлял ситуации связанные с экспортом функций, которые перекрывали стандартные знаки операций
Безусловно, есть некоторые выгоды, которые можно получить за счет усложнения правил видимость и структуры языка, однако, как показывает практика, недостатки, которые появляются наряду с этими преимуществами – перевешивают.
В современных ЯП модульная структура значительно упрощена, пусть и в ущерб гибкости.
Вспомним, что мы рассматривали модули как возможность определения новых типов данных – так в модуле мы один раз описываем структуры данных, процедуры и функции, а далее импортируем и используем ранее описанные элементы.
п.3. Понятие класса
Класс как языковый конструкт служит для определения нового типа данных (ТД) и обобщает понятие записи (за счёт того, что класс объединяет в одно место не только данные, но процедуры и функции). Не даром во многих современных языках понятие записи или вообще отсутствует (Java), либо является частным случаем класса.
Член класса:
- Данные;
- Функции и процедуры;
- Другие типы данных (перечисления, вложенные классы и т.д.)
-
в языках Cи++, Java, Cи#:
class имя_класса [наследование] //возможно отсутствует
{
определение члена
}
-
В языке Дельфи:
type X= class [(наследование)] //возможно отсутствует
объявление членов
end;
1. Большинство современных ЯП используют объектно-ссылочную семантику (все объекты класса всегда являются ссылками) - Delphi, Cи#, Java.
Объектно-ориентированные языки, которые не имеют объектно-ссылочной семантики - Си++ и Оберон (формально).
Почему все объекты в языках располагаются в динамической памяти, практика программирования объектно-ориентированных программ показала, что в большинстве случаев предсказать время жизни объекта тяжело, правда иногда это может быть легко – пример с диалогами, когда время жизни диалога ограничено одним блоком. Но такие случаи редки. Таким образом, даже в Обероне, в котором формально объект не имеет ссылочной семантики, большинство объектов реально располагаются в динамической памяти. Мы существенно упрощаем работу я языком, если заставим все объекты по определению размещаться в динамической памяти. Следовательно, объекты в этих языках появляются тогда, когда мы применяем некоторую операцию порождения.
в Си# и Java
X x; // объявление неинициализированной ссылки, любое обращение к которой ведет к прерыванию
x = new X(); // порождение самого объекта в Си# и Java
в Дельфи
x: XType;
x = XType.Create(); // create – специальная функция член класса – конструктор
Все объекты (даже если мы при объявлении класса явно не указали, откуда он наследуется) прямо или косвенно наследуются из единого объекта Object для Cи#, TObject для Дельфи. string и object (именно с маленькой буквы) - ключевые слова языка Cи#, это базисные типы данных. String и Object - классы-обёртки для типов string и object соответственно (из common language runtime).
В Дельфи общий предок для всех типов данных - TObject, у которого есть метод Create, поэтому у большинства классов конструктор тоже называется create, хотя мы можем присваивать другие имена конструкторам. Конструктор, который создает объект, загружаемый с внешней памяти, называют – load.
Создание объекта в языке Java: имя класса.имя_конструктора(); - мы явно вызываем некоторую функцию, которая является конструктором, можем передавать ей некоторые параметры. Компилятор вставляет вызов к менеджеру динамической памяти, который отводит в динамической памяти кусок, вызывает для этого куска конструктор и присваивает объекту ссылку на этот кусок.
X:= ....
new X() - явный вызов конструктора.
Очевидный вопрос: кто уничтожает объекты?
В Паскале есть new и dispose, а в языках Си#, Java и в Обероне применяется автоматическая сборка мусора. В таких языках используется техника mark_and_scan (см. в предыдущих лекциях):
-
Чем это хорошо: не надо явным образом вызывать оператор delete (с которым часто были ошибки).
-
Недостаток: в непредсказуемый момент времени программа может просто подвиснуть, запустив сборщик мусора.
Все эти языки специально поставляют некоторые пакеты, занимающиеся сборкой мусора, например, в Си# есть пакет GC (garbage collector), специальный класс, который отвечает за сборку мусора. Можно заставить его проработать в фиксированный момент времени, можно подавить вызов сборщика мусора, однако с точки зрения накладных расходов вызов GC – всегда достаточно «суровая» вещь. Писать программы, которые должны гарантировать отклик в реальном времени, на подобных языках весьма затруднительно.
Какая проблема порождается автоматической сборкой мусора (речь идет об автоматической утилизации только одного ресурса- динамической памяти): память - не единственный системный ресурс, который является критичным. Файлы, устройства ввода-вывода - критичные ресурсы. Любой класс захватывает не только динамическую память, но и кучу ресурсов. В какой момент освобождаются эти ресурсы? С динамической сборкой мусора мы не можем предсказать, когда освободится тот или иной ресурс. А если памяти достаточно на очень большое количество объектов? В языке Cи# и Java предусмотрены средства борьбы с данной проблемой. Си# есть специальный интерфейс IDisposable - метод IDispose вызывается для освобождения ресурсов, занимаемых данным объектом. Рекомендуется перед тем, как присвоить ссылке на объект NULL, выполнить метод IDispose (если объект поддерживает данный интерфейс и захватывает какие-нибудь объекты кроме динамической памяти). Вся это актуально для графических приложений – где иконки, кисти и пр. являются критичными ресурсами.
В Дельфи есть объектно-ссылочная модель, а никакой сборки мусора нет. И это несильно мешает программированию.
Какие операции связаны с классом:
Самая главная - доступ к элементам класса.
Чаще всего это оператор точка: имя_класса.имя-члена
Члены бывают статическими и нестатическими. Еще в 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;
}
}
То же самое может распространяться и на некоторые локальные переменные соответствующих функций, но не на соответствующие параметры.