Опубликован: 23.10.2009 | Уровень: для всех | Доступ: платный
Лекция 6:

Примитивные символьные данные

< Лекция 5 || Лекция 6: 12 || Лекция 7 >
Аннотация: В данной лекции дается определение понятия кодировок, "кодовых страниц" символов, а также приводятся основные понятия "примитивного символьного" типа данных и правилах работы с ними.
Ключевые слова: таблица символов, EBCDIC, ANSI, CP/M, контроль четности, OEM, KOI8-R, VAX, знакогенератор, кодирование символов, кодировка символов, длина, индекс, массив, система программирования, символьные типы, variant, unsigned, PUC, CSTRING, Си, пробельный символ, Паскаль, Basic, СУБД, x86, автор, константы, short, указатель, символьная константа, создание функций, MID, хакерские атаки, MEMS, strlen, strncat, strncmp, strncpy, strpbrk, strrchr, strspn, strstr, SIM, CNT, MSCS, w-buffer, argument, метасимволы, строковый элемент, последний элемент списка, chr, обратная функция, HEX, reversibility, списки значений, substring, lexicographic order, pos, управляющий символ, study, splice, список удалений, pop, CMP, unpack, кодировка, таблица, литеры, мэйнфрейм, система счисления, апостроф, возврат каретки, оператор условия, equipping, интегральная схема, примитивный тип данных, терминальный символ, индекс массива, динамические массивы, блок операторов, символьный массив, access denied

6.1 Кодировки

6.1.1. Представление символов целыми числами

Как уже указывалось в "лекции 5" , все символы в ЭВМ представлены в виде целых чисел. Порядок соответствия литер символов целым числам называется кодировкой (или кодовой таблицей, таблицей символов). В зависимости от размера таблицы символов разрядность этих целых чисел равна 1 байт (для кодировок EBCDIC и всех вариантов ASCII) , 2 байта (для кодировок Unicode) или переменной длины (UTF-8, UTF-7 и др.). Более подробно о кодировках будет сказано ниже.

6.1.2. Кодировки

Вначале возникли кодировки, включающие в себя десятичные цифры, буквы (прописные и строчные) латинского алфавита, сокращения английского языка (такие как "&" и "@"), знаки препинания и специальные символы. На основе этих символов возникли кодировки EBCDIC (более старая) и ASCII (более новая, используемая до сих пор). Преимущества кодировки ASCII в том, что расположение латинских букв в ней не только упорядочено по алфавиту (как это сделано во всех кодировках), но и расположены эти буквы "без разрывов", то есть присутствует сплошная нумерация букв от A до Z и от a до z. Это позволяет лучше реализовать алгоритмы сравнения символов и сортировку строк символов.

Замечание. Из-за особенностей русского алфавита, реализовать кодирование букв подряд не удается ни в одной из кодировок. Дело в том, что кириллическая буква "е" есть только в русском алфавите, но не входит в состав "стандартных" кириллических символов. Наиболее "правильно" русский алфавит представлен в кодировке ANSI cp 1251, USSR GOST и Unicode.

Кодировки EBCDIC и ASCII являются 7-ми битными кодировками, хотя они и занимают 1 байт. Самый старший (нулевой) бит является либо резервным, либо используется для контроля четности при передаче данных.

6.1.3. Восьмибитные кодировки

Однако в кодировке ASCII отсутствуют даже некоторые символы (умляуты) западноевропейских алфавитов. Что же говорить о кириллических символах... Для решения этой проблемы были созданы расширенные кодировки ASCII, в которых для представления символов используются все восемь бит. Это доводит количество закодированных символов до 256. Среди реализаций кириллических символов в расширенных ASCII кодировках можно отметить кодировки OEM 866 (MS-DOS), ANSI cp 1251 (Windows) и KOI8-R (все клоны UNIX и VAX).

Относительно полно концепция "сплошной нумерации" применяется в кодировке USSR GOST и ANSI cp 1251. Кодировка USSR GOST должна была заменить кодировку OEM 866 на компьютерах, производимых в СССР. Однако, по ряду причин, этого не произошло, и в настоящее время эта кодировка является "мертвой".

Исторически первой кодировкой с кириллическими символами была кодировка KOI8. В ней латинские буквы с кодами 128-255 были заменены кириллическими символами в той же транскрипции. К сожалению, эта кодировка сейчас почти сошла на "нет" по следующим причинам:

  1. Этот стандарт не поддержали производители компьютерного оборудования за рубежом;
  2. Отсутствие "сплошной" нумерации кириллического алфавита;
  3. Невозможность совместного использования кириллицы и умляутов;
  4. "Неправильная" реализация символов псевдографики.

Следом за этой кодировкой на российские компьютеры вначале пришла болгарская, а затем - и альтернативная русская кодировка ДОС, впоследствии переименованная в OEM 866. Преимущества этой кодировки:

  1. Компактность;
  2. Частично сплошная нумерация символов;
  3. Полная совместимость с MS-DOS;
  4. Корректная реализация символов псевдографики;
  5. "Нормальная" обработка символов на всех платформах;
  6. Наличие коммерческих и некоммерческих знакогенераторов для этой платформы.

Недостатками этой кодировки являются:

  1. Привязанность к MS-DOS, вместе с которой эта кодировка "умирает";
  2. Невозможность совместного использования кириллицы и умляутов;
  3. Общий недостаток всех восьмибитных кодировок - ограничение на кодирование только 256-ю символами.

Кодировка ANSI cp 1251 получила широкое распространение из-за продвижения продукции корпорации Microsoft, прежде всего Microsoft Windows и Microsoft Word, на Российский рынок. Эта кодировка имеет большие преимущества перед другими кириллическими кодировками:

  1. Компактность;
  2. Сплошная нумерация символов (кроме "е" и "Ё");
  3. Стандартность.

Однако у этой кодировки есть недостатки:

  1. Привязанность в основном к платформе Microsoft Windows;
  2. Некорректная обработка многими программами символов с номерами 254 и 255 ("ю" и "я");
  3. Невозможность совместного использования кириллицы и умляутов.

6.1.4.Многоразрядные кодировки

К "многоразрядным" кодировкам относятся кодировки Unicode и UTF. Рассмотрим историю их возникновения.

Уже в начале 90-х годов XX века стало ясно, что 256 чисел для кодирования символов очень мало. Например, с помощью этих чисел нельзя кодировать все символы европейских алфавитов. Поэтому появились русские, западноевропейские, восточноевропейские, балтийские, не говоря уже об арабских, турецких кодировках и кодировках на иврите, каждая из которых содержала 256 символов. Что же говорить о вьетнамском алфавите, корейских, японских и китайских иероглифах, каждые из которых содержат более 256 символов? Для этой цели был разработан специальный стандарт на кодировку символов 16-ти разрядными целыми числами (2 байта) - Unicode. Полностью с помощью 16-ти разрядных чисел можно представить более 65 тысяч символов, что с избытком хватит для кодирования всех литер основных языков земли.

При всей полезности кодировки символов Unicode следует отметить ее недостаток - вдвое больший размер кода, чем при использовании 8-ми разрядных кодировок. Этот недостаток особенно заметен при использовании только латинских букв. Поэтому вскоре был принят стандарт на кодировку UTF. В ней символы кодируются числом переменной разрядности: 7-ми разрядными кодами ASCII для латинских букв и 16-ти разрядными символами Unicode для всех остальных литер алфавита. За эту "экономию" приходится расплачиваться людям, пишущих на отличном от английского языка - работа с символами в кодировке UTF более сложная, чем в других кодировках. Это надо помнить!

Примечание. Кодировка UTF-8 стала стандартом в операционной системе Linux и в Интернете. При разработке приложений в Microsoft Window, начиная с версии 98, также широко используются кодировки UTF-8 и UTF-7.

6.2. Примитивные символьные данные

В качестве "примитивных" символьных данных чаще всего используются массивы "символьных" типов данных. При этом максимальная длина этого массива (то есть максимальный индекс, который может принимать этот массив) фиксируется либо программистом, либо системой программирования (например, конкретной реализацией компилятора).

В таблице 6.1 даются описания символьных данных, их размерности и обозначение.

Таблица 6.1. Символьные типы данных
Тип данных Префикс/Окончание Разрядность регистров/длина Примечание
Quick Basic
STR <идентификатор>$ Переменная длина (до 255 символов) Для хранения 1 символа в кодировке ASCII. Не защищен от изменений.
VB Script
Variant в зависимости от конкретного значения Переменная длина Используется для хранения всех видов данных.
Java
char с 2 байта Для хранения одного символа в кодировке Unicode
String str Переменная длина Класс для хранения символов в кодировке Unicode. Защищено от изменений!
javascript
variant в зависимости от конкретного значения Переменная длина Используется для хранения всех видов данных.
C/C++
char с 1 байт Для хранения одного символа в кодировке ASCII (со знаком)
unsigned char uc 1 байт Для хранения одного символа в кодировке ASCII (без знака)
char * pc от 2 до 4 байт Указатель на массив (переменной длины)
unsigned char * puc от 2 до 4 байт Указатель на массив (переменной длины)
C/C++ Windows API
BYTE b 1 байт Эквивалентен unsigned char
NPSTR npsz 2 байта Эквивалентен char near *
LPSTR lpch 4 байта Эквивалентен char far *
LPCSTR lpsz 4 байта Эквивалентен const char far *
C++ MFC
CString cstr, str Переменная длина Класс для хранения символов в кодировке Unicode. Защищено от изменений!
C++ Classlib
string str Переменная длина Класс для хранения символов в кодировке ASCII (и Unicode?). Защищено от изменений!

Обычно, в начале работы программы, весь массив символьных данных заполняют нулевой константой (она обозначается на языке Си как " \0 "). Это делается в расчете на то, что признаком конца символьных данных является не признак конца строки, не пробельный символ, а именно этот нулевой символ. Поэтому заполнение массива нулевыми символами заведомо гарантирует, что не случится ошибка типа размещения символьных данных за границами массива, после одиночного нулевого символа.

Что касается максимальной длины символьной строки, то она может принимать значения от 255 (языки "Паскаль", "Quick Basic", СУБД dBase III+), 2047 (Multi Edit и часть редакторов) и даже ~ 64 Кбайт (величина сегмента памяти процессора x86). Но, следующий нехитрый расчет показывает, что использовать длину строки более 2000 (80*25) символов нерационально (она все равно не уместится на экране).

Также автор советует не доверять своей памяти в использовании максимальной длины строки, а определить ее в виде константы целого значения (например, константного типа const unsigned short на языке Си).

Следующим "джентльменским соглашением" при работе с символьными данными является неизменность значений массивов символьных данных (кроме, возможно, первого массива в выражении) при символьных вычислениях. Вот почему большинство символьных функций принимает тип аргументов как const char * (указатель на начало массива символьных констант). Именно к этому типу необходимо принудительно приводить Ваши переменные в программе!

Итак, организация работы с "примитивными" символьными массивами данных следующая:

  1. Создается (функцией malloc ) или объявляется (в начале блока программ) необходимое количество символьных массивов-переменных;
  2. Они инициализируются нулевыми символами (функцией memset );
  3. Производятся операции с символьными данными:
    • ввод;
    • присвоение им константных значений;
    • замена, добавление или "обрезание" значений этих переменных;
    • вывод символьных значений;
  4. Для созданных переменных при завершении программы необходимо освободить память, размещенную при их создании (функцией free ).
Замечание: некоторые функции и операторы для работы с символьными данными (такие как MID$ в Quick Basic-е или Replace в некоторых библиотеках C/C++) могут изменять значения внутри массива символьных данных. Для соблюдения "джентльменского соглашения", необходимо сначала сделать копию изменяемой строки в виде отдельного массива, а уже в ней - изменять содержимое массива. Этот метод и безопаснее (в том числе и для противодействия хакерским атакам), и экономнее (память из-под ненужных символьных переменных можно сразу освободить).
Замечание. Необходимо всегда следить, чтобы индекс символьной переменной-массива не выходил за границы массива. Обычно при этом генерируется ошибка операционной системы, которая может быть перехвачена хакером, открывающая таким образом доступ к Вашей операционной системе и Вашему компьютеру. Чтобы этого не произошло, автор советует ограничить использование функций mem*** и полностью исключить использование функций str*** (за исключением функции strlen ), заменив их функциями: strn*** (на языке Си). Звездочками обозначено окончание функций.

Собственно стандартные функции для работы с символьными типами данных (в том числе и с примитивными данными) на языках Си и C++, вместе с комментариями, приведены в таблице 6.2.

Таблица 6.2. Функции для работы с текстом
Функция/Метод Описание Пример
Язык Си. Функции ANSI C
#include <string.h> Заголовочный файл для этих функций
int strlen( char *str ); Возвращает длину строки str1 (позицию его символа \0).
char* strerror( char *str );
char *strncat( char *str1, char *str2, unsigned int n ); Конкатенация строки str1 и str2, но в строку n записывается не более n символов. Возвращается значение строки str1 или NULL при неудаче конкатенации.
int strncmp( char *str1, char *str2, unsigned int n ); Сравниваются не более чем n символов в строке str1 со строкой str2 в лексикографическом порядке. Возвращает: "0" - если строки совпали, "-1" - если str1 < str2, "1" - если str1 > str2.
char *strncpy( char *str1, char *str2, unsigned int n ); Копирует строку str2 в строку str1, но не более чем n символов. Возвращается значение строки str1 или NULL при неудаче копирования.
char *strpbrk( char *str1, char *str2 ); Ищется первое вхождение символа из строки str2 в строке str1. Возвращает указатель на этот символ или NULL, если символы не обнаружены.
char *strrchr( char *str1, char *str2 ); Ищется последнее вхождение символа из строки str2 в строке str1. Возвращает указатель на этот символ или NULL, если символы не обнаружены.
char *strspn( char *str1, char *str2 ); Ищется первое вхождение символа, которого нет в строке str2, в строке str1. Возвращает указатель на этот символ или NULL, если символы не обнаружены.
char *strstr( char *str1, char *str2 ); Ищется первое вхождение подстроки str2 в строке str1. Возвращается указатель на заданную подстроку или NULL в случае ее необнаружения.
char *strtok( char *str1; char *str2 ); Функция разбивает строку str1 на слова, вставляя после каждого из них символ '\0'. Символы, являющиеся разделителями слов, перечислены в строке str2. При первом вызове функция возвращает указатель на str1. При последующем вызове (с параметром str1 == NULL) она возвращает указатели на другие слова в строке str1. Функция возвращает NULL, если все слова закончились.
char *memchr( char *buf, char sim, unsigned cnt ); Функция ведет поиск первого вхождения символа sim в не более чем cnt символов с начала строки buf. Возвращает указатель на символ sim в случае его нахождения или NULL если он не найден.
int memcmp( char *buf1, char *buf2, unsigned cnt ); Сравниваются не более чем cnt символов в строке buf1 со строкой buf2 в лексикографическом порядке. Возвращает: "0" - если строки совпали, "-1" - если buf1 < buf2, "1" - если buf1 > buf2. Символ '\0' в буфере рассматривается как обычный символ, а не признак конца строки.
char *memcpy( char *dst1, char *src2, unsigned int n ); Копирует строку src2 в строку dst1, но не более чем n символов. Возвращается значение строки dst1. Реализация функции гарантирует, что перекрывающие участки будут правильно обработаны.
Функции TURBO C и MSC (дополнительно)
#include <string.h> Заголовочный файл для этих функций
int memicmp(char *buf1, char *buf2, unsigned cnt ); Сравниваются не более чем cnt символов в строке buf1 со строкой buf2 в лексикографическом порядке. Возвращает: "0" - если строки совпали, "-1" - если buf1 < buf2, "1" - если buf1 > buf2. Регистр символов игнорируется. Символ '\0' в буфере рассматривается как обычный символ, а не признак конца строки.
char *memmove(char *buf1, char *buf2, unsigned cnt ); Функция полностью идентична функции memcpy. При копировании перекрывающихся участков гарантируется, что эти участки скопируются правильно.
char *memset( char *dest, char sim, unsigned cnt ); Функция присваивает буферу dest повторяющиеся cnt число раз значения символа sim. Возвращаемое значение - указатель на массив dest. Примечание: с помощью этой функции можно задать строку, содержащую в себе только пустые символы: '\0'. Этого невозможно сделать с помощью команды strnset.
char *strnset( char *str1, char sim, unsigned int n ); Функция присваивает буферу str1 повторяющиеся не более чем n число раз значения символа sim. Возвращаемое значение - указатель на массив str1.
int strnicmp(char *str1, char *str2, unsigned int n ); Сравниваются не более чем n символов в строке str1 со строкой str2 в лексикографическом порядке. Возвращает: "0" - если строки совпали, "-1" - если str1 < str2, "1" - если str1 > str2. Регистр символов игнорируется.
char *strlwr( char *str ); Переводит все символы в строке str в нижний регистр. Возвращает значение указателя на строку str.
char *strrev( char *str ); Функция меняет порядок следования символов в строке str. Возвращает значение указателя на строку str.
char *strupr( char *str ); Переводит все символы в строке str в верхний регистр. Возвращает значение указателя на строку str.
Функции TURBO C (дополнительно)
#include <string.h> Заголовочный файл для этих функций
char *strerror( int number );
Язык Си. Функции ANSI C
#include <stdio.h> Заголовочный файл для этих функций
int sprintf( char *buffer, char *format_string[, arguments]); Функция форматирует и записывает в строку buffer сформированную строку символов, задаваемой шаблоном-строкой format_string. Обозначение метасимволов в шаблоне аналогично программе printf.
< Лекция 5 || Лекция 6: 12 || Лекция 7 >
Юрий Фролов
Юрий Фролов
Украина
Ольга Галанина
Ольга Галанина
Россия