лекции (2003) (Глазкова) (1160821), страница 6
Текст из файла (страница 6)
ISO-10646. Был зафиксирован набор символов и система кодировки для западноевропейских языков.
При этом система кодировки была однобайтовая (каждый символ в этом наборе кодировался значением от 0 до 255), и первые 7 бит совпадали с кодом ASCII7 ( поэтому английские тексты представимы в любой системе кодировки), а оставшиеся биты отдавались национальным представлениям.
Главная проблема с точки зрения представления информации состояла в том, что набора символов от 0 до 255 не хватало.
Т.е. большинство компьютерных систем оперировали с символом как с 1 байтом. (это минимальная ячейка памяти, в которую можно записать 1 символ). Но такие программы могли работать только с западноевропейскими языками. Другие языки были вынуждены вводить свои системы кодировки. Для русского языка и до сих пор используется более 5 систем кодировок. Ни к чему кроме путаницы это не приводит.
Рассмотрим ОС WIN9x, которая поддерживает только 8 битные кодировки. Там, например, русский и западноевропейский текст нельзя одновременно использовать, т.к. они находятся в разных системах кодировки.
У японцев проблема представления стояла еще более остро, т.к. им для представления 1 байта не хватало ( в японский стандарт входит более 6000 иероглифов). Для представления текстов на японском и китайском языках были изобретены свои системы кодировки.
SBCS – это кодировка для европейских языков (single byte character set).
MBCS – символ определяется 1, 2 или 3-х байтовыми последовательностями (multi byte character set).
DBCS – эта кодировка была изобретена для китайского языка (каждый символ кодируется парой байт - double byte character set).
Сейчас проблемой стандартизации алфавитов заинтересовалось мировое сообщество.
Оно утвердило общий подход – 2 стандарта UCS-4 и UCS-2 (UCS – universal character set) – универсальный набор символов.
В UCS-2 любой символ представим двумя байтами. Кодировка UNICODE – это фактически представление в UCS-2 ( различие только в представлении дальневосточных символов).
Чем хорош UNICODE – решена проблема совместимости. Каждому языку выделен свой диапазон в UNICODE ( например, для русского языка – 0х440, для арабского - 0х400 и т.д.).
При этом для западноевропейских языков представление в UNICODE совпадает с представлением в ISO-10646, иначе говоря, для представления западноевропейского текста в UNICODE достаточно к каждому символу добавить нулевой байт.
Т.о., мы обсудили проблему представления текстов.
Проблема представления это часть более общей проблемы: интернационализации (i18n)
Это проблема разработки международного программного обеспечения. Часто под интернационализацией понимают только проблему локализованного программного обеспечения. На самом деле эта проблема сводится к двум:
-
локализации
-
глобализации.
Проблему локализации можно решить и без UNICODE. Глобализация приложения – одна версия должна работать для всех языков.
Проблема представления наиболее остро встает при глобализации. Пример глобального приложения: любое web-приложение (приложение «клиент-сервер»).
С развитием интернета проблемы создания глобальных приложений стали наиболее острыми (т.е. одно и то же приложение должно работать в разных языковых средах).
Без UNICODE полностью решить проблему создания именно глобального программного обеспечения невозможно. Заметим, что в UNIX с самого начала большое внимание уделялось созданию программного обеспечения ОС, которые поддерживали интернационализацию. К сожалению, UNIX развивался под влиянием англоязычных программистов. Они к проблемам представления других языков относятся скептически.
Рассмотрим теперь символьный тип языков программирования. Основная проблема – проблема представления. Общая тенденция всех ЯП – поддержка UNICODE. В современных ЯП ( Java, C#) , появившихся в 90 гг., символьный ТД char – это двухбайтовая последовательность в кодировке UNICODE.
Во всех старых ЯП, использующих однобайтовое представление, появляется новый специальный символьный тип WideChar – 2 байтовая последовательность в кодировке UNICODE.
Во всех ЯП символьный ТД – это отдельный ТД, за исключением С и С++( там символьный ТД – разновидность арифметических ТД), и для того, чтобы привести его к целочисленному типу надо явным образом произвести преобразование типов.
Рассмотрим язык Аda.
При создании языка Аda проблемам представления уделялось очень большое внимание, и этот язык должен обладать очень высокой степенью мобильности.
Создатели языка Аda рассматривали тип CHARACTER не как тип , встроенный в базис языка, а как частный случай перечислимого типа. Чем удобна такая трактовка?
В зависимости от системы и версии языка, на котором реализуется программа, мы вводим свой новый перечислимый тип.
Тип CHARACTER – это тип из стандартной библиотеки языка, который поддерживал символы от 0 до 127 в кодировке ASCII.
В новых версиях языка появился ТД WideCHARACTER.
Язык – это система компромиссов.
Рассмотрение символьного типа как перечислимого позволило на том уровне решить проблему переносимости.
Но, как следствие, возникают проблемы.
-
В языке Pascal константа перечислимого типа – это идентификатор. Как символы можно представить в виде идентификаторов?! Поэтому в Аде в качестве константы перечислимого типа можно было указывать литерал, т.е. заключённый в кавычки любой представимый в данной системе символ.
-
В языке Pascal идентификатор перечисления должен быть уникальный. Следовательно, нельзя было бы объявить 2 перечислимых типа, которые пересекались хотя бы по одной константе. А различные символьные ТД однозначно пересекаться не могут.
Создатели языка АДА должны были пойти на компромисс: они это допустили за счет перекрытия операций (у разных функций может быть одно и то же имя), и литерал перечисления трактуется как нульместная функция (т.е. функция без аргументов), которая выдает константное значение.
Но помимо всего этого нужен контекст.
Пусть есть две процедуры:
Procedure P(C: Color);
Procedure P(X: ScreenColor);
Это допустимо и называется перекрытием операций, когда двум разным процедурам соответствует одно имя P.
Если компьютер видит запись P(a); то он смотрит тип a и в зависимости от этого поступает по-разному(это называется принципом статического разрешения перекрытий).
Но рассмотрим вызов P(red); - здесь контекста конструкций не хватает.
Здесь нужна специальная операция – уточнение типа (это не есть преобразование типа). Эта конструкция записывается так:
тип ‘выражение’.
Чтобы был корректный вызов надо уточнить:
либо P(Color ‘red’);
либо P(ScreenColor ‘red’);
Если уточнения нет, то компилятор выдает сообщение об ошибке.
Пункт 5. Порядковые типы данных.
Это диапазоны и перечисления.
Впервые эти типы данных появились в языке Pascal.
Диапазоны.
Для диапазона есть базовый ТД и константы L и R.
Что может быть базовым типом диапазона? Это дискретный ТД - атомарный ТД, для которого должны быть применимы операции SUCC и PRED ( т.е. дискретные ТД являются линейно упорядоченными).
PRED/SUCC – получение предыдущего/следующего по порядку. Для min/max значения не определена операция PRED/SUCC соответственно. Из рассмотренных ТД плавающие ТД не являются дискретными, т.к. операции PRED/SUCC на разных реализациях будут работать по разному. Фиксированный ТД (delta-тип языка Аda ) свойству дискретности удовлетворяют. Т.о. диапазоны задаются на базе дискретного типа.
Диапазоны ограничивают изменчивость и вводят понятие квазистатического контроля. Диапазон – очень популярный ТД. Например, в ЯП АДА общий вид задания диапазона следующий:
Базовый тип range L..R
Почему в ЯП АДА и Modula надо уточнять базовый тип, а в Pascal – нет.
Т.к. в Паскале можно ввести правило: тип каждой константы определяется ее внешним видом. В языках АДА и Modula 2 по виду константы не всегда можно догадаться к какому виду она относится (именно поэтому нужно указывать базовый тип).
Рассмотрим теперь новые ЯП: C# Java. В этих, как и в языках Оберон и С++, диапазонного ТД нет.
Почему?
Языки C# и Java – это чистые объектно-ориентированные ЯП; а Оберон – это минимальный ЯП, который поддерживает объектно-ориентированный стиль.
Диапазон же является избыточным и противоречит объектно-ориентированному стилю (в ООР стиле основные понятия : наследование и динамический полиморфизм, а диапазонный ТД с этими понятиями не согласуется).
Почему в С++ не включено понятие диапазона?
Массив в С++ - это просто поле памяти (без квазистатического контроля). Запись P[-1] ошибку не выдаст (т.к. это почти эквивалентно записи P-1, а это корректное выражение). Вместо расширения базиса языка, создатели языка С++ усовершенствовали средства развития. Концепция классов (даже без наследования) и шаблонов позволяет реализовать структуры типа вектора и диапазона.
Т.о. диапазонный тип противоречит ООР стилю программирования, и современные средства программирования позволяют смоделировать это понятие.
С перечислимым ТД ситуация аналогичная. Впервые он появился в языке Pascal, он есть в Си, Аде, С++, С# ; в Java и Обероне его нет.
Лекция 7
Перечислимый тип данных.
Часто возникает потребность задать некоторое ограниченное множество допустимых значений для какой-либо переменной.
Перечислимый тип – тип, в описании которого перечислены все возможные значения, являющиеся символическими константами.
Type T = (const1, const2,…, constN ), т.е. тип данных мы явно описываем через множество констант. С точки зрения реализации эти константы выглядят как целые числа от 0 до N-1, где N – мощность множества констант. Представление перечисления как объекта данных в памяти компьютера достаточно просто: каждому элементу перечисления при выполнении программы сопоставляется неотрицательное целое число ( его порядковый номер в последовательности).
Основные операции над объектами перечислимого типа – это операции отношения, операции присваивания и операции succ и pred, которые для данного элемента перечисления определяют соответственно последующий и предыдущий элементы.
Перечислимый ТД является дискретным ТД и хорош, когда требуются выборки, т.е. конструкцию дискретного выбора по n направлениям очень удобно описывать перечислимым типом.
Например, если Direction задается типом integer и имеет значения 0,1,2,3, то соответствующий контекст неясен. А если Direction описать через перечисление (West, South, East, North), то контекст становится понятен. В перечислении Direction имеется всего 4 различных элемента, которые во время выполнения программы представляются как 0= West,1= South, 2= East и 3= North, поэтому для представления этих элементов в памяти компьютера достаточно двух битов.
Итак, объявление перечислимого типа вводит мнемонические (отражающие содержательную роль) имена для компонент модели решаемой задачи и представляет полезную абстракцию от конкретной кодировки. Перечислимые тип данных резко повышают:
-
надежность (т.к. вместо какого-то целого ТД мы имеем явным образом перечисленное множество);
-
документируемость (подразумеваемые значения переменной задокументированы внутри самой программы);
-
эффективность (т.к. компилятор может подбирать для перечислимых ТД оптимальный размер памяти).
Чего же не хватало в перечислимых ТД, например в стандартном Pascal.
Не хватало явно определенной связи между перечислимым ТД и целым.
Преобразование из перечислимого ТД в целый тривиально и совершенно безопасно. Например, в языке Modula2 допустимо преобразование INTEGER(T); такое же преобразование допустимо и в языке Ада.
Интересно также обратное. Например, в Modula2 есть псевдофункция Val(T,C); где Т – перечислимый ТД, а С – выражение целого ТД. Эта функция либо возвращает константу с соответствующим порядковым номером в типе данных Т, либо аварийным образом завершает выполнение программы. Т.е. это примерно то же самое, что и преобразование Т (integer) в языке Ада.
Т.о., всегда можно переходить от перечислимого типа к целому и от целого к перечислимому.