лекции (2004) (1160823), страница 6
Текст из файла (страница 6)
Можно ли преобразовывать перечислимый тип в числа и наоборот?
Кроме С++(по наследству от С) неявно нельзя.
type T=(C1,…,CN)
X:T;
X:=T(8);-явное преобразование числа в константу(квазистатический контроль).
X:=color(i);
В модуле-2:
Val(T,e) Т-имя типа, e- значение.
Языки программирования. Лекция 7.
На прошлой лекции обсуждались арифметические типы данных и перечислимый тип. Перечислимый тип является разновидностью порядкового типа, который основан на понятии целочисленного типа данных.
Порядковые типы:
-
Перечисление
-
Диапазон
Перечислимый тип
В современных ЯП существуют две точки зрения относительно перечислимых типов:
1. перечислимые типы усложняют язык, ничего не внося, т.е. и надежность, и документированность у них есть, но они противоречат чисто объектно-ориентированному стилю – они не расширяемы. Именно поэтому из некоторых современных объектно-ориентированных языков перечисление убрано вовсе – Оберон, Java. Вместо перечисления используются наборы целочисленных констант.
2. однако в ряде языков, отметим Си#, Си++, перечисление осталось. Из соображений совместимость перечисление в Си++ носит на себе все «родимые пятна» перечисления из языка Си. Так перечисление в Си есть ни что иное, как просто удобный способ именования констант от 0 до N-1.
Кроме того, пусть
enum C {C1,C2, …, Cn};
enum C c;
int x;
x = c; // допустимо, так как присваивается значение от 0 до N-1
Таким образом, в Си есть неявное преобразование из перечислимого типа в целочисленный, что безопасно, хотя сравниваются идейно различные величины. Хуже то, что допустимо такое присваивание:
c = x; // допустимо для любого х
можно написать с=-1; что формально никакого смысла не имеет, но, тем не менее, допустимо. Такой перечислимый тип используется исключительно для короткого способа задания набора целочисленных констант от 0 до N-1.
В Си++ такой подход с одной стороны был неприменим – если мы называем что-либо типом данных, то это должен быть тип данных, например на основе типа данных мы должны иметь право устраивать перекрытия. И, следовательно, в Си++ мы имеем право писать:
void f (enum c);
void f(int i);
И это все должно быть допустимо в одной и той де области видимости.
С одной стороны неявные преобразования перечислимого и целочисленного типов нехороши. Хотя есть определенное удобство в применении некоторых целочисленных операций к перечислимому типу.
Например, в языке Паскаль порядковые типы (дискретные) отличались от остальных наличием двух операций:
-
succ –взятие следующего элемента (аналог прибавления единицы у целочисленного типа)
-
pred – взятие предыдущего элемента (аналог вычитания единицы у целочисленного типа)
Эти операции использовались в циклах, так в цикле for можно было употреблять любой дискретный тип, поскольку там использовались succ и pred. А циклы типа for в Паскаль использовались для того, чтобы бродить по массивам – ходить по диапазону индексов.
Теперь ситуация с массивами простая – все массивы начинаются с 0 и заканчиваются на N-1, следовательно нужда в перечислениях как в диапазонах просто отпадает.
Как мы уже отмечали, перечислимый тип – это, прежде всего удобный способ именования констант – одновременно именуем и присваиваем значение. Одним из частных случаев констант являются флаги. Для флагов перечисления хороши – так как если используется сложение и вычитание элементов перечислимого типа – то можно использовать целочисленный тип, а вот именно для флагов удобно использовать логические комбинации – побитовые операции and и or. К сожалению, создатели языка Си++ не мог воспользоваться такими возможностями, так как:
-
надо было сделать enum нормальным тд
-
обеспечить совместимость с Си
Поэтому в Си++ принято другое решение – над элементами перечислимого тд допускаются произвольные целочисленные операции, т.е. существует неявное преобразование из любого enum в int, а обратное преобразование может быть только явным, при этом никаких действий по проверке компилятор не предпринимает. Языки Си и Си++ характеризует отсутствие квазистатического контроля. Квазистатический контроль – это контроль, если соответственно компилятор знает значение, он выполняет его статически, а если не знает, то откладывает, программирует эту проверку на период выполнения. В языках с квазистатическим контролем
V:=const; // если это недопустимо и выдается сообщение об ошибке – то выполняется статический контроль
V:=x; // выполняется квазистатический контроль, проверка откладывается
В Си, как высокоуровневом ассемблере (в ассемблере контроля вообще нет), квазистатический контроль отсутствует, как следствие крайне высокая эффективность программ и никакого лишнего кода. Тот же принцип и в основе Си++, если программист хочет какой-то квазистатический контроль то пусть его и вставляет, т.е. если что-то не проверяется на этапе выполнения оно же не будет проверяться на этапе компиляции.
Пример:
С с;
int x;
x = c; // абсолютно допустимо и неявное преобразование
c = (enum C) x; // написанное таким образом не вызывает не довольства компилятора, никаких действий по проверке не предпринимается, и если х = -1 , то все на усмотрение программиста.
И дополнительно в Си++ появилась удобная возможность непосредственного присваивания значений константам:
C {c1 = -1; c2 = 0, c3 = 1, …, cn = (любое константное выражение)} – это особенно удобно, если под элементами перечисления понимаются флаги - read = 1, write = 2, readwrite = комбинация read и write. Такие перечисления не очень надежны, зато наглядны и более или менее достигается совместимость со старыми программами на Си. Базовым типом для любого перечисления является целочисленный.
В Си# оставлено перечисление не смотря на то, что это объектно-ориентированный язык. Причина в том, что Си# ориентируется как базовый язык платформы .Net, в которой поддерживается как чисто объектно-ориентированный стиль программирования (так Си# обладает большинство черт языка Java), так и элементы императивного и даже компонентного программирования. В Си# императивное программирование поддерживается в специальных конструкциях unsafe{}, эта конструкция говорит о том, что в блоке употребляются потенциально небезопасные конструкции. Такими конструкциями можно считать:
1. указатели (указатели в стиле языка Си)
2. обращения к runtime библиотекам языка Си – такие как malloc и free (в .Net основная библиотека Net Framework безопасна. Например, в внутри коллекций всегда осуществляется контроль за индексами, мы не можем достать не существующий элемент, не можем испортить коллекцию. Аналогично для распределения динамической памяти используется один и тот же алгоритм для всех языков, для всех элементов библиотеки)
3. если класс помечен как unsafe, то все функции-члены могут использовать небезопасные конструкции
При пометке unsafe компилятор языка Си# генерирует такой же код, как генерировал бы компилятор языка Си++.
А так как Си# должен поддерживать компонентное программирование то поддерживают следующую парадигму – язык предназначен и для использования готовых компонент, которые написаны на любых языка системы .Net, и для написания соответствующих компонент. Удобней всего писать компоненты на Си#, хотя также можно использовать Visual Basic и управляемый Си++. Поэтому и остались перечисления, ведь компоненты – это своеобразный «черный ящик», который не надо наследовать, а которые надо только использовать в бинарном виде. Как создатели Си# обошли проблемы, которые стоят перед перечислим типом:
1.расширяемость – не нужно использовать перечисления, там где речь не идет о расширяемости, т.е. о создании готовых компонент
Например, текстовый редактор – ничего от него не надо наследовать, только использовать. И при выравнивании текста можно создать перечислимый тип из трех элементов – влево, вправо, по центру.
2. эффективность – есть возможность управления базовым типом перечисления
общий вид перечисления такой:
enum имя: базовый_тип //произвольный целочисленный тд, по умолчанию int
{
имя = значение,
имя = значение,
};
3.была такая проблема – импорт имени перечислимого типа влечет за собой импорт целой совокупности имен. В Си# решено так, что соответствующие перечислению имена констант локализованы внутри перечисления – доступ только имя_типа.имя_константы.
Enum Color {red, green, blue} и Color.red – правильное обращение и конфликта имен просто не возникает.
Кроме того, подчеркнем, что в Cи# есть еще понятие класса и понятие пространства имен и доступ к элементам как внутри класса, так и в нутрии перечисления, так и внутри пространства имен осуществляется только через оператор «.»( точка).
4. надежность – в Си# нельзя неявным образом преобразовывать одни перечислимые типы в другие и перечислимые типы в целочисленные (это не касается константы 0, там особая ситуация). Необходимы явные преобразования, которые являются контролируемыми.
Color c;
c = 1; // ошибка
c = (Color) 1; // правильно
c = (Color) (-1); // ошибка
int x;
x = c; //ошибка
c = x; //ошибка
c = (Color) x; //квазистатический контроль
x = (int) c; //преобразования не будет – проверка диапазона, например могут быть разные целочисленные типы, кроме того подчеркивается разница типов
5. при использование флагов введена конструкция – допускающая операции and и or
[flags]1 enum OpenMode {Read = 1, Write = 2, ReadWrite = Read | Write}
Диапазон
Впервые этот тип появился в Паскале, и диапазон можно было установить над любым дискретным типом (таким чтобы присутствовали операции succ и pred). Диапазон – это набор значений базового типа, который ограничен значениями L и R, при этом L <= R.
Синтаксис может различаться.
1. в Модуле 2: [L..R]
2. в Ада: базовый_тип range L..R;
Базовый тип должен присутствовать в определении диапазона если по внешнему виду L и R (константы) не понятно к какому типу они относятся. Ада любые различные типы несовместимы, а в Модуле 2 есть два целочисленных типа – integer и cardinal, которые несовместимы. Если базовый тип опущен – то используется integer.
Диапазоны присутствовали в Модула 2, Ада, но в Java, Си# и Оберон никаких диапазонов нет, что связано с реализацией массивов, так в 90% случаев диапазоны используются как диапазоны массивов. Во многих языках массивы имеют установленный диапазон от 0 до N-1, для альтернативных пользовательских диапазонов используются схемы, сводящие их к стандартным схемам.
В Фортране массивы начинались с 1, но начинать их с 0 эффективней, ведь тогда A[i] - *(A+i), а другом случае *(A+i-1). В Алголе60 неявно появилось понятие диапазона, которое развилось в Паскале. Процесс перехода от произвольного отрезка к (0, N-1) отражает снижение доли научных расчетов в общей доли программ. В современный языках диапазон сам по себе не нужен – ведь можно написать любой необходимый класс.
Символьный тип
Раньше компьютеры предназначались для обработки числовой информации, сейчас же основная задача – обработка символьной информации.
Во всех современных языках (появившиеся в 90-ые годы – Си# и Java) есть два момента заслуживающих рассмотрения.
1.рвут страницы интерпретации символьного типа, как числового. В Си и Си++ char – арифметический тип, в остальных языках символьные типы данных никак не связаны с арифметическими, хотя преобразования из integer в char всегда есть – в Паскаль (ord,chr). В Аде символьный тип – просто перечислимый тип.
2.проблема использования национальных языков.
Так проблема русификации возникала на западных машинах с 7-битной кодировкой. Изначально программы ориентировались в большинстве своем на западноевропейские языки. Присутствовали две основные кодировки:
ASCII-7 – 7-битная кодировка (американский стандартный код для обмена информации), символы с номерами от 0 до 127
ISO-Latin-1 – символы с кодом от 0 до 127 совпадают с ASCII, кроме того был символы от 128 до 255 (использовались для западноевропейских языков), не входят польский, хорватский, венгерский и прочие восточноевропейские языки.
Проблемы возникли, так как на всю группу индоевропейских языков 256 символов не хватает, и многие языки не из основной группы поддерживались неадекватно, отсюда и появились проблемы кодовых страниц, проблема charset (набор символов). Все символы необходимо было свести к набору от 0 до 255, которого просто не хватало. Решение было следующим – ASCII поддерживалось всеми, а остальное занималось национальными кодами, так появились разнообразные кодировки. Для кириллицы – CP1251, KOИ8-Р, DOS 866 и прочие (для почтовых систем, для удобной печати).
В принципе все было нормально, до тех пор, пока не пришлось использовать иероглифические языки – никакая кодовая страница не помогала.
-
Проблема появилась на самом деле когда IBM выбрала единицей адресуемой памяти байт - 8 битов, посчитав, что 8 битов будет вполне достаточно для кодировки английского языка и языков потенциальных заказчиков (страны за железным занавесом не рассматривались, азиатские страны тоже). Сформировалось понятие SBCS – Single Byte Character Set, появилась кодировка – ДКОИ-8.
-
Позже японцы изобрели MBCS (multi, где символ может занимать от 1 до N байтов), требовалось 13 битов, однако все компьютеры байтоориентированы. Кодировка JIS – использовала 2 байта, но ее не было в компьютерах, тогда возникла кодировка Shifted JIS -
А)если символ от 0 до 127, то это символ из ASCII-7
Б)если символ больше 127, то он оккупирует 2 байта и его значение из JIS
В такой системе символы ASCII-7 отображаются как обычно, а для прочих нужны специальные устройства отображения.
-
Китайского, тайского и корейского языков изобрели DBCS (double) – для всех символов нужны специальные устройства отображения.
Решение стандартизации нашли в виде Universal Character Set (UCS). Зафиксировали значения – и каждому алфавиту выделили свой диапазон значений – юникодовская зона, например, кириллической зоне соответствуют коды >= 0х401. а для максимальной совместимости первые 256 символов совпадают с ISO-Latin-1. UCS-2 – двухбайтный стандарт, позже UCS -4 – четырехбайтный стандарт. При этом Unicode 3.0 примерно соответствует UCS-2 (различия в кодировке некоторых японских символов). UCS-4 получался из UCS-2 дописыванием нулей в 2 старших байта. В результате одна и та же программа может обрабатывать данные на различных языках.
Windows 95, 98, ME не использовали Unicode, как следствие в notepad (который сделан на базовом элементе управления) нельзя использовать одновременно русский, английский и например французский – так как для SBCS надо выбрать фиксированный code page – либо 1251 (для англ. и русс.), либо ISO-Latin-1 (франц. и англ.).