На этом шаге мы продолжим рассмотрение примеров использования строковых
функций, в
частности, функций, позволяющих осуществлять поиск в строках.
Поиск символов
Поиск подстрок
Разложение строк на подстроки
Часто программам приходится выполнять поиск в строках отдельных символов или подстрок, особенно при проверке имен файлов на заданное расширение. Например, после того как пользователю предложили ввести имя файла, проверяется, ввел ли он расширение .ТХТ, и если это так, то выполняется действие, отличное от того, какое было бы выполнено для расширения .ЕХЕ.
Возможно, вы также захотите отвергнуть все расширения, кроме определенного, что поможет вам предотвратить ошибки, вызванные загрузкой файла данных нежелаемого типа.
Пример 1 показывает, как использовать функцию strchr() для поиска отдельных символов в строке.
#include <iostream.h> #include <string.h> #include <stdio.h> void main() { char *filename = new char[128]; cout << "Задайте имя файла: "; gets(filename); cout << "\nЗаданное имя файла: " << filename << endl; if (strchr (filename,'.')) cout << "Имя уже содержит расширение" << endl; else strcat (filename,".TXT"); cout << "Окончательное имя файла: " << filename << endl; delete [] filename; }
Данная программа находит расширение в имени файла, выполняя поиск точки среди символов введенной строки. (В имени файла может быть только одна точка, которая должна предшествовать расширению, если оно имеется.) Ключевым в этой программе является оператор
if (strchr (filename,'.')) cout << "Имя уже содержит расширение" << endl; else strcat (filename,".TXT");
Рис.1. Функция strchr() находит символ в
строке
Рисунок 1 демонстрирует еще один важный момент, связанный с адресацией указателем не полной строки, а ее части - подстроки. Такими указателями следует пользоваться с большой осторожностью. На рисунке показана только одна строка, TEST.ТХТ, оканчивающаяся нулевым байтом, но два указателя - filename и p. Указатель filename адресует полную строку. А указатель p адресует подстроку внутри того же набора символов. Строковые функции не заботятся о байтах, которые предшествуют их первому символу. Поэтому оператор
puts(p);
В программировании на C++ нет ничего необычного в использовании многих указателей, адресующих подстроки одной и той же полной строки. Но строка, показанная на рис. 1, расположена в куче, поэтому оператор
delete [] filename;
корректно освобождает занимаемую строкой память. Однако программа никогда не должна выполнять оператор вида
delete [] p;
пытаясь тем самым освободить подстроку, адресуемую указателем p, что, несомненно, приведет к разрушению кучи, вызвав ошибку, относящуюся к разряду трудно обнаруживаемых.
Функция strchr() отыскивает первое появление символа в строке. Объявления и операторы
char *p; char s[]="Abracadabra"; p = strchr(s,'a');
присваивают указателю p адрес первой строчной буквы 'а' в строке "Abracadabra".
Функция strchr() рассматривает завершающий нуль строки как значащий символ. Приняв во внимание этот факт, можно узнать адрес конца строки. Учитывая предыдущие объявления, оператор
p = strchr(s,0);
установит p равным адресу нулевого символа в строке s. Используйте этот простой метод, чтобы найти конец строки.
Чтобы найти последнее появление символа в строке, вызовите функцию strrchr(). Учитывая предыдущие объявления, оператор
p = strrchr(s,'b');
установит указатель p равным адресу подстроки "bra" в конце строки "Abracadabra".
Кроме поиска символов в строке, вы также можете поохотиться и за подстроками. Пример 2 демонстрирует этот метод. Данная программа аналогична примеру 1, но устанавливает расширение файла .ТХТ.
#include <iostream.h> #include <string.h> #include <stdio.h> void main() { char *filename = new char[128],*p; cout << "Задайте имя файла: "; gets(filename); cout << "\nЗаданное имя файла: " << filename << endl; strupr(filename); p = strstr (filename,".TXT"); if (p) cout << "Имя уже содержит требуемое расширение" << endl; else { p = strchr (filename,'.'); if (p) *p=NULL; //Удалить любое другое расширение. strcat (filename,".TXT"); } cout << "Окончательное имя файла: " << filename << endl; delete [] filename; }
Эта программа создает имя файла, которое обязательно заканчивается расширением .ТХТ. Чтобы определить, есть ли в имени файла это расширение, программа выполняет оператор
p = strstr (filename,".TXT");
Подобно strchr(), функция strstr() возвращает адрес подстроки или нуль, если искомая строка не найдена. Если же цель будет обнаружена, указатель p установится равным ее адресу, в данном примере - адресу точки в подстроке .ТХТ. Поскольку расширение может быть введено и строчными буквами, программа выполняет оператор
strupr(filename);
чтобы перед вызовом функции strstr() преобразовать буквы оригинальной строки в прописные.
Программа примера 2 также демонстрирует способ усечения строки в позиции заданного символа или подстроки. Здесь вызывается функция strstr(), чтобы установить указатель p равным адресу первой точки в строке filename. Если результат этого поиска не нулевой, то выполнится оператор, который запишет вместо точки нулевой байт:
*p = NULL;
Тем самым будет присоединен новый конец строки в том месте, где раньше находилось расширение файла. Теперь строка готова к добавлению нового расширения путем вызова функции strcat().
Прикладное программирование часто требует нахождения компонентов строки, или лексем; этот процесс называется синтаксическим анализом. Если составные части строки отделены друг от друга запятыми, пробелами или другими разделителями, вы можете использовать функцию strtok() для разложения строки на несколько подстрок.
Рассмотрим следующие объявления:
char s[] = "Мама мыла раму с мылом"; char *p1, *p2, *p3, *p4, *p5;
Строка s является инициализированным символьным массивом. Пять указателей на тип char пока что не инициализированы (мы будем из использовать при разборе строки), адресуемой указателем s. Сначала вызовем функцию strtok(), передав в качестве первого аргумента указатель на строку, а в качестве второго - разделительный символ (представленный в данном случае строкой, состоящей из одного символа пробела):
p1 = strtok(s," ");
Функция strtok() возвращает адрес первого компонента, отделенного от следующего заданным разделителем: в данном примере - пробелом. Разделитель должен представлять собой строку, состоящую из одного символа. Например, чтобы разложить строки на элементы, разделенные запятыми, используйте в качестве второго аргумента функции strtok() строку ",", а не символ ','. Рисунок 2 иллюстрирует состояние переменных программы как раз на этой стадии.
Рис.2. После первого вызова strtok()
Как показано на рисунке 2, функция strtok() вставляет нулевой байт (представленный на рисунке как '\О') в позицию первого заданного ограничителя. Тем самым создается маленькая подстрока, адрес которой возвращается функцией strtok(), а в данном примере присваивается переменной p1. Если заданные разделители не обнаружены, функция strtok() возвращает нуль. Кроме того, эта функция настраивает свой внутренний указатель на символ, следующий за концом последней сформированной подстроки, чтобы последующий вызов strtok() мог продолжить разложение строки. Для этого передайте в качестве первого аргумента значение NULL - это послужит сигналом для функции strtok() использовать ее внутренний указатель как стартовый адрес для поиска другого разделителя. Таким образом, чтобы выделить другие компоненты строки, нужно просто вызвать функцию strtok() несколько раз, и каждый раз первым ее аргументом должен быть NULL:
p2 = strtok(NULL," "); p3 = strtok(NULL," "); p4 = strtok(NULL," "); p5 = strtok(NULL," ");
На рисунке 3 показана разобранная строка и ее указатели от p1 до p5. Каждый указатель адресует завершающуюся нулем подстроку внутри первоначальной строки. Каждая из подстрок теперь является отдельной строковой переменной, и эти пять указателей можно передавать другим строковым функциям, таким как strlen(), strcpy() и strdup().
Рис.3. После разбора строки с помощью функции
strtok()
В предыдущем примере предполагалось, что заранее известно, из скольки компонентов состоит строка. В большинстве же случаев заранее это неизвестно, поэтому адресация компонентов с помощью индивидуальных компонентов на практике не применяется.
Чтобы выделить составляющие строки, более разумно было бы использовать цикл, в котором результаты работы функции strtok() передавались бы другим строковым функциям или сохранялись. Такой цикл обычно выглядит следующим образом:
p = strtok(buffer,";"); while (p) { //Обработка подстроки, адресуемой указателем p. p = strtok(NULL,";"); }
Сначала указатель p устанавливается равным результату функции strtok(), которой были переданы указатель на исходную строку buffer и разделитель ";". Затем цикл while проверяет значение указателя p на равенство нулю. Если указатель p имеет ненулевое значение, то он адресует очередную подстроку в строке buffer и вы можете передавать его другой строковой функции. После обработки найденной подстроки внутри цикла осуществляется попытка поиска других подстрок с помощью нового вызова функции strtok(), но теперь в качестве первого аргумента передается значение NULL.
Пример 3 показывает, как использовать функцию strtok() для выделения слов из строки. Скомпилируйте и запустите программу. Затем (когда программа предложит сделать ввод) введите строку, состоящую из слов, разделенных пробелами. После этого будет проведен анализ сделанного вами ввода и отображение его результатов в виде отдельных слов.
#include <iostream.h> #include <string.h> #include <stdio.h> void main() { char buffer[128],*p; cout << "Задайте строку, слова в которой разделены пробелами:\n"; gets(buffer); cout << "\nЗаданная строка: " << buffer << endl; cout << "Ее разбиение на слова: " << endl; p = strtok (buffer," "); while (p) { cout << p << endl; p = strtok (NULL," "); } }
На следующем шаге мы закончим изучение использования строковых функций.