Лекция 5 (1160838), страница 2
Текст из файла (страница 2)
Возникает проблема:
procedure P(X:SemiColor);
procedure P(X:BasicColor);//и такая перегрузка допустима!
P(Yellow);//понятно, какая функция вызовется
P(Red); или P(Green) ;// компилятор выдаст ошибку, непонятно, какую функцию вызывать.
Приведения типа нет. Зато есть уточнение типа, никак не связанное с его преобразованием, обозначающее просьбу компилятору трактовать данное выражение как выражение соответствующего типа.
T’expr;//уточнение типа
P(BasicColors’Red);//корректно!
Возвращаясь к ЯП Ада: ввод-вывод.
Компилятор Ады помнит представление перечислимого типа и выводит на экран в случае надобности слово Red и остальные.
Проблема управления тоже решена.
For BasicColors use
(
Red->FF0000X
Green=>FF00X
Blue =>FFX
)
C помощью спецификатора for можно задавать соответствующие значения.
Для подобной задачи достаточно было всего трех байтов.
for BasicColors’Size//можно специфицировать размер соответствующего типа.
Перечислимый тип в Deplhi – все унаследовано от Pascal.
Перечислимый тип в C#, Java
C#- внешний синтаксиси тот же:
enum BasicColors
{
Red=0xFF0000;
Green=….;
Blue=…;
}
Но существует семантическая разница:
можно выбирать базовый тип(int, правда, выбран по умолчанию:), к примеру:
enum SemiColors: byte{
Red
Yellow
Green
}
Безусловно, проблема управления представлением присутствует.
В С++ и в Си многие программисты употребляли значения перечислимого типа данных как флаги. В С# неявных преобразований нет, поэтому эту проблему решили через атрибуты – средства сообщать компилятору о реализации данных типов.
pragma
#pragma – указание компилятору. Например,
#pragma inline.
Однако такой формат плох, он строго зависит от реализации, нестандартизован.
Заслуга С# в этом плане в том, что он
-
стандартизован
-
связан с библиотекой ЯП.
Средства в ЯП, которые позвволяют отражать свойства программного текста, называются рефлексией.(реализваны из основных ЯП в C# и Java).
Атрибуты – служат для управления реализацией соответствующего перечислимого типа данных.
Пример
С помощью директивы [Flags] мы указываем, что к значениям соответствующего перечислимого типа данных можно применятть побитовые операции. К примеру:
[Flags]
enum FileAccept{
Read=1;
Write=2;
ReadWrite=Read|Write }
Еще пример.
Пусть SC – переменная типа SemiColors.
# pragma inline
byte b=(byte)SC;//разрешено преобразовывать переменные типа Semicolor к типу byte,
Можно считать, что именно константы являются локальными в определении класса. Заметим, что сами имена в C# стало легче проектировать. Это очень хорошо согласуется с системой программирования(императивные подсказки!!!) решение проблемы 4(см страницу 5) – удобство использования(обеспечивается тем, что для любого из перечислимых типов существует класс-обертка).
С# также решает проблему 1, обеспечивая существование значений классов.
В ЯП С#, как мы знаем, все данные бывают двух типов: это референциальные типы данных(классы, массивы, интерфейсы) и простые типы данных –НЕ классы(целочисленные, булевские, символьные и пр.). К простым типам данных относится и перечислимый тип данных.
Вообще говоря, полезно рассматривать любые данные как обьект. Пример: коллекция: я хочу держать в ней различные(все) типы данных. Это удобно, (однако встает проблема: что есть 2 +3 С точки зрения языка SmallTalk, например, это посылка сообщения: сообщение с именем «+» посылается обьекту «2» с параметром(именем, аргументом) «3». Далее происходит просмотр таблицы методов доступа, обпабатывающий сообщение «+». Все, казалось бы, хорошо, но неэффективно. в C# и Java от такого подхода отказались. В них(языках, казалось бы, претендующих на звание чисто обьектно-ориентированных) существуют простые типы данных – просто так эффективней.
Зато для всех типов данных в C# и Java существуют классы-обертки.
Тип данных | Обертка в Java | Обертка в C# |
int | Integer | Int32 |
bool | Boolean | Boolean |
long | Long | Int64 |
Table1. Посвящена классам-оберткам.
Проблема возникает как раз на подходе к перечислению. Специально для него существуют специальные классы-обертки. Все они запечатаны(sealed в C#), или финальные(final в Java). Но компилятор неявно выводит, что все перечислимые типы данных – это методы класса enum, который является производным от класса Object(сейчас мы говорим про Шарп), что обеспечивает возможность получить все характеристики созданного нами типа во время выполнения программы.
Для любого перечислимого типа данных существует метод Enum[] GetValues(), который по типу обьекта перечислимого типа данных возвращает нам его значения(точнее, массив из возможных значений).
В классе Enum есть статический метод GetValues():
Enum.GetValues(typeof(FileAccess));//в скобокчках пишется соответствующий RTTI.
Теперь можно пробегать все значения и распечатывать их, например.
Вывод: в C# обеспечено максимальное удобство работы с перечислимым типом данных.
А как обстоит дело в Java?
Java 5.0(Tiger) -2005 год – выход новой спецификации языка Java.
enum SemiColors{ Red, Yellow, Green }
Синтаксис – как в C#,
Новшество:
SemiColors.Red;//обращение к статическим членам класса Элементы enum на самом деле – членынекоторого классаи определение SemiColor задает определение некоторого класса. А Red, Yellow, Green – константы – это статические члены класса, представляющие собой значения этого класса..
Правда, в отичие от С#, элементы перечислени нельзя наследовать. И в отличие от C#, ввиду того, что константы – это обьекты классов – управления представлением нет.(Можно, конечно, воспользоваться методом ToString(), который получает значение константы, и сравнивать константы таким образом.)
Кроме всего прочего, в классе Enum есть метод ordinal(), который выдает номер константы перечислимого типа(то есть значения перечислимых типов данных упорядоченны).
Например:
ordinal(Red)==0, а ordinal(Yellow)==1.
Можно еще получить SemiColor.Value(“Red”)=0; - очень удобно работать со вводом.
Перечислимый тип может, являясь классом, содержать дополнительные чены. Например:
enum Apples{
Int price;
Apples(int p);//конструктор приватный, так как инициализация присходит внутри системы
{price=p;}
public void setprice(int p)
{ price=p; }
public int getprice()
{ return price; }
}
Теперь каждое значение перечислимого типа данных Apples должно иметь цену.
Вывод: трактовка обьектов перечислимого типа данных как обьектов класса многое позволяет(НО: константы перечислимого типа не могут наследоваться).
Существует только единственная возможность инициализации:
Обьекты перечислимого типа данных нельзя копировать(тогда произойдет копирование ссылок, что не есть хорошо). А реально копировние происходит при вызове метода Loan(), однако вызывать его для обьектов перечислимого типа анных не стоит(буде исключение). Да это и неразумно.
Пункт 2.4.2. Диапазон
Под словом «диапазон» в данном случае понимается диапазон значений некоторого типа даных. Впервые тип данных Диапазон появился в языке Pascal.
var x:L..R;//любое имя –с амоидентифицирующееся, по имени востанавливается его тип.
Замечание. var x: 0..25; //плохо! Все константы должны быть именованны.
Диапазон в Modula-2
var x: [0..N]
vay y: CARDINAL [0..N]
var i:INTEGER;
var j:CARDINAL;
Поскольку обьекты типа диапазона «ограничены», при любой операции с ними происходит квазистатический контроль.
x:=0;//проверка при компиляции – все хорошо
x:=i;//компилятор вставит квазистчатический контроль(проверка происходит во время выполнения программы)
x:=j;//ошибка! Квазистатическая проверка не вставляется, это CARDINAL(безнаковые и знаковые типы смешивать нельзя, а ип-диапазон по умолчанию – подмножество INTEGER – знакового типа данных.)
Точно так же и в Аде:
type Diap is range 0..N;//по умолчанию относится к типу данных integer
Пример.
Пусть существует некоторый тип:
type Pos new INTEGER range 0..max-int
I:INTEGER;
Y:Pos;
Y:=I;//ошибка! – тип разные(из-за new)
Можно сделать так: Y:=Pos(I);//тогда компилятор вставит квазистатическую проверку, если I не будет принадлежать диапазону, случится range_error.
Еще пример.
subtype NATURAL is range 1..MAX_INT
I=i;//ошибки нет
j=I;//квазистатическая проверка
Ни в одном современном ЯП типа данных диапазона нет. Его коцепции противоречит концепция расширения типов, поэтому его нет в Обероне.
Почему же их нет в Java и в C#? Статистических данных по использованию программистами типа данных диапазона нет, программисты ограничиваются индексами массивов. Диапазоны оказались не нужны, они просто выпали из современных языков программирования.
Пункт 2.4 Указатели и ссылки.
Указатель – это абстракция адреса. Адрeс может вести к
-
имени обьекта данных
-
указателю
-
к метке(в тех ЯП, где есть оператор goto)
Адрес – это низкоуровневое понятие.
Если есть метка:
Собственно адрес относится к первой из перечисленных трех категорий.
Стандарт Pascal
VAR I: T;
TYPE pT=^T;
Modula-2
TYPE P=POINTER TO T;
Указатель служит для работы с анонимными обьектами в динамической памяти.
Указатели в языке Pascal
В Pascal нет динамической сборки мусора, для освобождения памяи, как мы знаем, существует оператор DISPOSE(p); Кроме того, мы знаем, что в данном языке существует операция взятия адреса. Она обеспечивает возможность множества ошибок.
Итог
Стандартный Pascal, Modula-2 и Ada – строгие ЯП. Указатели в них служат только для работы с динамической памятью.
Указатели в C, C++
& - операция взятия адреса
Пример.
T * p;
void * pp;
pp=p;//можем писать везде, так как любой конкретный адрес является еще и абстрактным адресом
p=(T*)pp;//тоже можно. Именно потому, что возможно такое преобразование, возникает множество ошибок динамической памяти.
Вывод: 95% ошибок в таких ЯП связаны именно с работой с динамической памятью.
Пример: работа с удаленной памятью:
New(P);
Dispose(p);
Мусор – это обьект из динамической памяти, на который не ссылается ни один указатель.
Пример.
p, p1: PT;
new(p);
new(p1);
p=p1;//мусором стал тот обьект, на который указывал p.
В этом смысле системы делятся на 2 класса: системы с динамической сборкой мусора и системы без динамической сборки мусора(должен существовать dispose или его аналог). У систем без динамической сборки мусора 2 проблемы:
-
«мусор»
-
проблема «висячих» ссылок
Языки программирования соответственно бывают
-
строгие(указатель служит только для работы с динамическими обьектами)
-
нестрогие(указатель предназначен для работы с любым адресом)
T* p;
Void f(){
T x;
P=&x;
}
Формально это корректно.
Но часто встречается ситуация, подобная следующей:
void Foo()
{
f();
free(p);//возникает ”висячая” ссылка
}
Но висячая сылка может быть и в строгих ЯП:
new(p);
p=p1;
dispose(p);//p1 висит!!!!!!
p=NIL;//p1 все равно висит