Скачиваний:
58
Добавлен:
04.03.2014
Размер:
81.41 Кб
Скачать

ОБРАБОТКА СИМВОЛЬНЫХ СТРОК

Символьная строка - основная структура данных в прикладных программах обработки текстовой информации. В терминах языка программирования C под строкой символов понимается любая последовательность символов, которую завершает нулевой код '\0'. В прикладной программе символьная строка может быть задана как строковая константа или как одномерный массив символов. Строковая константа определяется как набор символов, который заключен в двойные кавычки и неявно содержит завершающий символ с нулевым кодом. Следующий пример иллюстрируют два способа определения символьной строки "Unix" в форме строковой константы.

#define OS "Unix" /* Макроопределение строковой константы */

char* os = "Unix"; /* Указатель строковой константы */

Если символьная строка задается в форме одномерного массива, то все его элементы должны иметь тип char или (unsigned char), а последний символ должен иметь нулевой код. Способ формирования строки символов в массиве определяет выбор класса памяти для хранения его содержимого. Формирование строки символов в символьном массиве любого класса памяти обеспечивает явное присваивание желаемых кодов его элементам. Следующий блок исходного кода образует символьную строку "www" в массиве класса auto из четырех элементов.

{

char web[4];

web[0] = web[1] = web[2] = 'w';

web[3] = '\0';

}

Во внешнем или статическом массиве требуемая строка символов может быть получена путем инициализации его байтов содержимым строковой константы непосредственно при определении массива. Например, следующая декларация заставит компилятор выделить четыре байта для размещения статического массива и заполнить их кодами символов инициализирующей строки.

static char wm[] = "kde";

Аналогичный результат будет получен, если использовать менее компактную универсальную схему инициализации внешних и статических массивов любых типов как показано в следующем примере:

static char wm[] = { 'k', 'd', 'e', '\0' };

В обоих случаях нет необходимости явно задавать размер массива, потому что его автоматически определяет число инициализирующих символов. Если размер символьного массива указан явно и его значение больше требуемой длины строки, то лишние хвостовые элементы будет заполнять нулевой код.

При любом способе формирования доступ к символьной строке обеспечивает указатель типа (char *) или (unsigned char *), который содержит адрес начального символа строки. Такой принцип адресации позволяет передавать строки символов для необходимой функциональной обработки. Основными операциями функциональной обработки строк символов являются: измерение длины, копирование, конкатенация, сравнение, поиск и лексический разбор.

Система программирования C предоставляет обширный набор стандартных функций, которые реализуют перечисленные операции обработки символьных строк. Имена этих функций начинаются с префикса str, а их объектный код сосредоточен в стандартной библиотеке объектных модулей. Их программный интерфейс обеспечивает заголовочный файл <string.h>, где декларированы типы аргументов и кодов возврата всех функций обработки строк символов. Краткое описание принципа действия и применения этих функций, где они группируются по характеру выполняемых операций, приведено ниже.

Измерительная операция применяется, когда нужно определить длину строки или ее фрагмента. Длина строки вычисляется в байтах и равна числу ее символов до завершающего нулевого кода '\0'. Для измерения длины строки обычно используется функция strlen, спецификация формата вызова которой имеет вид:

unsigned strlen(const char* str);

Обращение к функции strlen возвращает длину символьной строки, которая адресована значением str. Например, вызов strlen("Linux") возвращает значение 5. Практическое использование функции strlen поясняет исходный код прикладной процедуры strcut, которая обрезает строку, адресуемую указателем longstr, если ее длина больше значения параметра limit.

/* Ограничение длинной строки */

void strcut(char* longstr, unsigned limit) {

if(strlen(longstr) > limit)

*(longstr + limit) = '\0';

} /* strcut */

Менее известны измерительные функции strspn и strcspn. Они вычисляют длину начального фрагмента строки, который, соответственно, составляет и исключает ограниченный набор символов. Спецификации формата их вызова имеют вид:

unsigned strspn (const char* str, const char* delimit);

unsigned strcspn(const char* str, const char* delimit);

Обе функции вычисляют максимальную длину начального фрагмента исходной строки, адресуемой указателем str, с учетом содержимого ограничительной строки, которую адресует указатель delimit. При этом функция strspn выявляет начальный фрагмент исходной строки, который состоит только из символов ограничительной строки. Функция strcspn, наоборот, определяет начальный фрагмент исходной строки, который не содержит символов из ограничительной строки. Обе функции передают результат вычислений через код возврата. Например, вызов функции strspn("happy", "path") возвратит значение 4, а обращение к функции strcspn при тех же аргументах вернет значение 0.

Действие функций strspn и strcspn можно рассматривать как поиск индекса позиции первого символа исходной строки, который, соответственно, не принадлежит или, наоборот, принадлежит кодовому набору ограничительной строки. В такой трактовке данные функции могут быть полезны, если нужно выделить информативную часть заданной строки. Например, исходный код прикладной функции deblank демонстрирует как обеспечить пропуск всех начальных пробелов и табуляций, используя функцию strspn:

/* Пропуск начальных пробелов и табуляций */

char* deblank(char* str) {

return(str + strspn(str, " \t"));

} /* deblank */

Для модификации содержимого символьных строк в прикладных программах наиболее часто применяется операция копирования. Полное или частичное копирование заданной строки символов обеспечивают функции strcpy и strncpy. Спецификации формата их вызова имеют вид:

char* strcpy (char* dest, const char* src);

char* strncpy(char* dest, const char* src, unsigned num);

Обе функции предназначены для копирования содержимого исходной строки, адресуемой указателем src, в массив символов, назначенный указателем dest. Функция strcpy обеспечивает полное копирование исходной строки, включая завершающий символ с кодом '\0'. Функция strncpy копирует не более, чем задано ее параметра num, начальных символов исходной строки. Когда значение параметра num превышает длину исходной строки, ее копию в назначенном массиве автоматически дополняет соответствующее число символов с нулевым кодом '\0', чтобы общее число символов, записанных в назначенный массив, стало равно num. Если, наоборот, исходная строка по длине больше или равна значению параметра num, то копия исходной строки в назначенном массиве не будет дополнена символом с нулевым кодом '\0'. Чтобы обеспечить корректность копирования, в функции strcpy и strncpy через указатель dest должен быть назначен массив, который имеет размер, достаточный для размещения всех символов копии исходной строки. При успешном завершении обе функции возвращают адрес полученной копии, то есть значение указателя dest.

Следующий фрагмент исходного кода демонстрирует использование функций strcpy и strncpy для инициализации и модификации содержимого массива символов в классе auto данными двух строковых констант:

char os[6];

strcpy (os, "Xenix"); /* инициализация os */

strncpy(os, "Mistake", 2); /* модификация os */

В приведенном примере вызов функции strcpy инициализирует символьный массив os, образуя в нем строку "Xenix". Вызов функции strncpy заменяет первые два символа массива os началом строковой константы "Mistake", превращая "Xenix" в "Minix".

Еще один способ формирования символьных строк предоставляет операция конкатенации. Она применяется для слияния содержимого заданных строк символов в одну строку. Эту конструктивную операцию обработки строк символов реализуют функции strcat и strncat. Спецификации форматов их вызова имеют вид:

char* strcat (char* dest, const char* src);

char* strncat(char* dest, const char* src, unsigned num);

Обе функции выполняют конкатенацию двух символьных строк, добавляя копию всего или части содержимого исходной строки, адресуемой указателем src, в конец строки, назначенной указателем dest. При слиянии строк, символ с нулевым кодом '\0', который завершает строку, назначенную указателем dest, перекрывается начальным символом исходной строки по адресу src. В конец полученной конкатенации автоматически добавляется нулевой код '\0', образуя строку символов, которая доступна по адресу, назначенному указателем dest. Содержимое исходной строки (по адресу src) остается неизменным. Различие обеих функций конкатенации состоит только в том, что функция strcat рассматривает для слияния всю исходную строку, а функция strncat только ее начальную часть, которая содержит не больше символов, чем задано значением параметра num. Если значение параметра num превышает длину исходной строки, то действие функции strncat будет эквивалентно вызову функции strcat. Следует отметить, что корректного выполнения конкатенации необходимо всегда иметь по адресу, назначенному указателем dest, объем памяти, который достаточен для размещения обеих сливаемых строк. При успешном завершении обе функции возвращают адрес полученной конкатенации, то есть значение указателя dest.

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

unsigned char sen[64]; /* Массив для символов предложения */

strcpy(sen, "Язык");

strcat(sen, " ");

strcat(sen, "программирования");

strcat(sen, " ");

strcat(sen, "Си.");

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

"Язык программирования Си."

Как известно это название монографии американских авторов Б. Кернигана и Д. Ритчи, которая была опубликована в 1978 году и с тех пор неофициально считается стандартом дефакто языка программирования C.

При обработке текстовой информации часто бывает необходимо сопоставить содержимое заданных символьных строк, выполнив операцию сравнения. Эта операция устанавливает отношение лексиграфического порядка для пары сравниваемых строк путем последовательного анализа кодов их символов на основе следующего формального правила. Пусть индексы i и j обозначают номера символов сопоставляемых строк V и W необязательно равной длины. Тогда строка V считается лексиграфически больше, чем строка W, если существует значение индекса j, при котором выполняются следующие соотношения:

V[j] > W[j] и V[i] == W[i], для всех значений индексов i < j.

Лексиграфическое равенство строк символов означает, что они идентичны. Например, строка символов "Windows XP" лексиграфически больше, чем символьная строка "Windows NT", потому что для кодов их первых различных символов N и X имеет место отношение порядка 'X' > N'. По аналогичным причинам символьная строка "Windows 98", лексиграфически больше, чем строка символов "Windows 2000".

Для полного или частичного сравнения символьных строк предусмотрены функции strcmp и strncmp. Спецификации формата вызова этих функций имеют вид:

int strcmp (const char* str1, const char* str2);

int strncmp(const char* str1, const char* str2, unsigned num);

Обе функции выполняют лексиграфическое сравнение двух строк, адресуемых указателями str1 и str2, последовательно сопоставляя коды их символов. При этом число символов, которые анализирует функция strncmp ограничено значением параметра num. Это позволяет сравнивать начальные части двух строк или символьных массивов. Функция strcmp не имеет параметрических ограничений по диапазону сравнения. Ее следует использовать для полного сравнения заданных строк. Однако, обе функции прерывают сравнение, если достигнут символ с нулевым кодом в любой из адресуемых строк. Поэтому действие функции strncmp эквивалентно обращению к функции strcmp, когда значение параметра num превышает длину любой из сравниваемых символьных строк. Результат сравнения обе функции передают через код возврата. Его значение может быть больше или меньше 0 в зависимости от того, какая из адресованных строк лексиграфически больше, первая или вторая. Во многих реализациях ненулевой возврат равен разности кодов первых несовпадающих символов сравниваемых строк. Нулевой возврат всегда имеет место, когда сравниваемые строки символов идентичны.

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

static char* operator[] = { "break",

"case",

"continue",

"default",

"do",

"else",

"for",

"if",

"return",

"switch",

"while",

NULL };

Для организации поиска заданного слова в принятой конфигурации данных может быть реализован следующий исходный код, который оформлен в виде прикладной функции findword.

/* Словарный поиск */

int findword(char* dic[], char* word) {

int i = 0; /* словарный индекс */

int rez = (-1); /* флаг результата сравнения */

/* Цикл поиска слова */

while(dic[i] != NULL) {

/* Проверка очередного слова словаря */

if((rez = strcmp(dic[i], word)) < 0)

i++;

else

break;

} /* while */

/* Возврат результата поиска */

return((rez == 0) ? i : (-1));

} /* findword */

При вызове в функцию findword необходимо передать адреса словарного массива и искомого слова, которые обозначают указатели dic[] и word, соответственно. Для поиска заданного слова функция fidword реализует последовательность сравнений содержащей его строки со строками слов словаря, используя функцию strcmp. Поиск продолжается, пока строка, содержащая текущее слово, лексиграфически меньше, чем строка искомого слова и не достигнут нулевой указатель NULL в конце словаря. При успехе поиска функция findword возвращает словарный индекс найденного слова, а при неудаче - отрицательный код (-1).

Не менее важной поисковой задачей является поиск требуемой информации в строке символов. Различные варианты организации операции поиска данных в символьной строке реализуют стандартные функции strchr, strrchr, strstr и strpbrk. Они позволяют обнаружить и установить расположение в строке определенного символа, символов заданного набора или фрагмента текста. Спецификация форматов их вызова имеет вид:

char* strchr (const char* str, int code);

char* strrchr(const char* str, int code);

char* strpbrk(const char* str, const char* key);

char* strstr (const char* str, const char* key);

Первый аргумент этих функций, обозначенный указателем str, передает адрес исходной строки символов, где требуется найти символьный объект, специфицированный вторым аргументом: code или key. Все функции должны возвращать адрес искомого объекта исходной строки, если он обнаружен, или нулевой указатель NULL, в противном случае.

Функции strchr и strrchr предназначены для поиска в исходной строке, соответственно, первого и последнего экземпляра символа, целочисленный код которого задает аргумент code. Все символы, которые сопоставляются при поиcке, интерпретируются как данные типа char, поэтому актуально только значение младшего байта параметра code. Завершающий символ с нулевым кодом '\0' считается частью исходной строки, то есть может быть обнаружен при поиске.

Функции strpbrk и strstr используют при поиске данные ключевой строки, адрес которой передается через указатель key. В частности, функция strpbrk ищет первый символ исходной строки, который совпадает с одним из символов ключевой строки. Завершающие символы с нулевым кодом '\0' в расчет не берутся. Функция strstr обеспечивает поиск первого вхождения в исходную строку последовательности символов ключевой строки без учета завершающего символа с нулевым кодом '\0'.

В прикладных программах рассмотренные функции могут быть использованы для контекстного поиска или замены заданных информационных объектов в строке символов. Их применение демонстрирует следующий исходный код упрощенной функциональной реализации прикладной команды basename из OS UNIX, которая выделяет базовое имя из заданного маршрутного имени файла.

/* Базовое имя файла */

char* basename(char* path, char* ext) {

char* b; /* указатель базового имени файла */

char* e; /* указатель расширения имени файла */

/* Найти адрес базового имени */

if((b = strrchr(path, '/')) != NULL)

b++;

else b = path;

/* Отделить расширение */

if((e = strstr(b, ext)) != NULL)

*e ='\0';

return(b); /* Вернуть адрес базового имени */

} /* basename */

При вызове в функцию basename следует передать адреса символьных строк, содержащих маршрутное имя и расширение имени файла, которые обозначают указатели path и ext, соответственно. В OS UNIX маршрутное имя файла образует префикс из цепочки названий подкаталогов, разделенных символом '/', которую завершает базовое имя файла с последующим произвольным и необязательным суффиксом расширения. Расширение имени файла начинается с символа '.', как принято для любых операционных системах, в том числе, для OS UNIX. Таким образом, расположение базового имени ограничивает с одной стороны адрес первого символа после последнего префиксного кода '/' и адрес суффикса расширения с другой стороны. Эти адреса в функции basename предоставляют вызовы функций strrchr и strstr. Первый символ расширения '.' заменяет нулевой код '\0', чтобы отделить расширение от базового имени файла. Префикс подкаталогов исключает возврат адреса базового имени файла. Например, следующий вызов функции basename:

char* base = basename("/usr/include/string.h", ".h");

возвращает указателю base адрес символьной строки "string", выделяя из маршрутного имени файла /usr/include/string.h базовое имя string.

Наиболее интеллектуальной операцией обработки символьных строк является операция лексического разбора. Она обеспечивает разбиение содержимого исходной символьной строки на отдельные текстовые единицы, лексемы. При этом предполагается, что в исходной строке лексемы разделены символами из заданного набора. Выделение лексем в исходной строке символов при заданных разделителях реализует функция strtok. Спецификация формата ее вызова имеет вид:

char* strtok(char* str, const char* delimit);

Функция strtok рассматривает исходную строку символов, адресуемую через указатель str, как набор лексем, которые разделены в ней промежутками из одного или нескольких символов строки ограничителей, адресованной указателем delimit. В приведенной транскрипции функция strtok выполняет поиск первой лексемы исходной строки символов. Поиск остальных лексем гарантирует последовательность вызовов функции strtok, в которых адрес исходной строки заменяет нулевой указатель NULL, а строки ограничителей необязательно идентичны. Каждое из последовательных обращений к функции strtok определяет адрес очередной лексемы исходной строки и запоминает его, чтобы обеспечить продолжение лексического разбора. Первый символ промежутка после обнаруженной лексемы в исходной строке автоматически заменяет нулевой код '\0'. Это позволяет получать лексемы в формате символьных строк, но приводит к изменению содержимого исходной строки лексического разбора. Поэтому при необходимости следует сохранить копию исходной строки перед вызовом функции strtok. Результаты лексического разбора образует конечная последовательность адресов лексем исходной символьной строки, которые возвращают очередные вызовы функции strtok. Признаком завершения лексического разбора является возврат из функции strtok нулевого указателя NULL, когда очередная лексема не может быть обнаружена.

Следующий фрагмент C-кода иллюстрирует применение функции strtok для лексического разбора строки символической записи обыкновенной дроби с целью выделить последовательности цифр числителя и знаменателя дроби, которые разделяет обязательный символ '/' и необязательные пробелы.

char* nominator; /* адрес числителя дроби */

char* denominator; /* адрес знаменателя дроби */

char fraction[] = " 1 / 1000"; /* строка дроби 1/1000 */

nominator = strtok(fraction, "/ "); /* выделение числителя */

denominator = strtok(NULL, " /"); /* выделение знаменателя */

В этом примере два последовательных вызова функции strtok для исходной строки fraction, содержащей дробь " 1 / 1000", выделяют две лексемы: числителя "1" и знаменателя "1000", адреса которых сохраняют указатели nominator и denominator, соответственно. Они могут быть использованы, например, для последующих целочисленных преобразований и обработки.

В заключение следует отметить, что во многих популярных версиях системы программирования C набор стандартных функций обработки символьных строк расширяют дополнительные процедуры, уникальные только для данной конкретной реализации. В частности, в системе программирования Borland C дополнительно к стандартным введены, например, функции strlwr и strupr, которые обеспечивают преобразование содержимого заданной строки к символам нижнего и верхнего регистра, соответственно. Однако, при разработке мобильного программного обеспечения целесообразно применять только стандартные функции обработки символьных строк, чтобы исключить зависимость исходного кода программы от особенностей конкретных реализаций системы программирования C.

Соседние файлы в папке Инфа - бесценно