Выбрать главу

Но должны ли вы обращать строку? С помощью rbegin и rend для обратной строки можно использовать все методы или алгоритмы, работающие с диапазонами итераторов. А если требуется выполнить поиск в строке, то можно использовать rfind, которая делает то же, что и find, но начинает с конца строки и движется к ее началу. Для больших строк или большого количества строк обращение может оказаться очень дорогостоящим, так что при возможности избегайте его.

4.6. Разделение строки

Проблема

Требуется разделить строку с разделителями на несколько строк. Например, может потребоваться разделить строку "Name|Address|Phone" на три отдельных строки — "Name", "Address" и "Phone", удалив при этом разделитель.

Решение

Для перехода от одного вхождения разделителя к следующему используйте метод find класса basic_string, а для копирования каждой подстроки используйте substr. Для хранения результатов используйте любую стандартную последовательность. Пример 4.10 использует vector.

Пример 4.10. Разделение строки с разделителями

#include <string>

#include <vector>

#include <functional>

#include <iostream>

using namespace std;

void split(const string& s, char c, vector<string>& v) {

 string::size_type i = 0;

 string::size_type j = s.find(c);

 while (j != string::npos) {

  v.push_back(s.substr(i, j-i));

  i = ++j;

  j = s.find(c, j);

  if (j == string::npos)

   v.push_back(s.substr(i, s.length()));

 }

}

int main() {

 vector<string> v;

 string s = "Account Name|Address 1|Address 2 |City";

 split(s, '|', v);

 for (int i = 0; i < v.size(); ++i) {

  cout << v[i] << '\n';

 }

}

Обсуждение

Превращение приведенного выше примера в шаблон функции, принимающий любой тип символов, тривиально — просто параметризуйте тип символов и замените случаи использования string на basic_string<T>.

template<typename T>

void split(const basic_string<T>& s, T c,

 vector<basic_string<T> >& v) {

 basic_string<T>::size_type i = 0;

 basic_string<T>::size_type j = s.find(c);

 while (j != basic_string<T>::npos) {

  v.push_back(s.substr(i, j-i));

  i = ++j;

  j = s.find(c, j);

  if (j == basic_string<T>::npos)

   v.push back(s.substr(i, s.length()));

 }

}

Логика при этом не меняется.

Однако обратите внимание, что между двумя последними угловыми скобками в последней строке заголовка функции добавлен один пробел. Это требуется для того, чтобы сказать компилятору, что это не оператор сдвига вправо.

Пример 4.10 разбивает строку с помощью простого алгоритма. Начиная с начала строки, он ищет первое вхождение разделителя с, а затем считает, что все, что стоит после начала строки или предыдущего найденного вхождения и до этого вхождения, является очередным фрагментом текста. Для поиска первого вхождения символа в оригинальной строке string пример использует метод find, а для копирования символов диапазона в новую string, помещаемую в vector, — метод substr. Это тот же самый принцип, который используется в функциях разбиения строк большинства скриптовых языков и является специальным случаем разделения строки текста на лексемы (tokenizing), описываемого в рецепте 4.7.

Разделение строки, использующей единственный символ-разделитель, является очень распространенной задачей, и неудивительно, что ее решение есть в библиотеке Boost String Algorithms. Оно просто в использовании. Чтобы увидеть, как разделить строку с помощью функции split из Boost, посмотрите на пример 4.11.

Пример 4.11. Разделение строки с помощью Boost

#include <iostream>

#include <string>

#include <list>

#include <boost/algorithm/string.hpp>

using namespace std;

using namespace boost;

int main() {

 string s = "one,two,three,four";

 list<string> results;

 split(results, s, is_any_of(",")); // Обратите внимание - это boost::split

 for (list<string>::const_iterator p = results.begin();

  p != results.end(); ++p) {

  cout << *p << endl;

 }

}

split — это шаблон функции, принимающий три аргумента. Он объявлен вот так.

template<typename Seq, typename Coll, typename Pred>

Seq& split(Seq& s, Coll& c, Pred p,

 token_compress_mode_type e = token_compress_off);

Seq, Coll и Pred представляют типы результирующей последовательности, входной коллекции и предиката, используемого для определения, является ли очередной объект разделителем. Аргумент последовательности — это последовательность, определенная по стандарту C++, содержащая нечто, что может хранить части того, что находится во входной коллекции. Так, например, в примере 4.11 был использован list<string>, но вместо него можно было бы использовать и vector<string>. Аргумент коллекции — это тип входной последовательности. Коллекция — это нестандартная концепция, которая похожа на последовательность, но с несколько меньшими требованиями (за подробностями обратитесь к документации по Boost по адресу www.boost.org). Аргумент предиката — это объект унарной функции или указатель на функцию, которая возвращает bool, указывающий, является ли ее аргумент разделителем или нет. Она вызывается для каждого элемента последовательности в виде f(*it), где it — это итератор, указывающий на элемент последовательности.

is_any_of — это удобный шаблон функции, поставляющийся в составе String Algorithms, которая облегчает жизнь при использовании нескольких разделителей. Он конструирует объект унарной функции, которая возвращает true, если переданный ей аргумент является членом набора. Другими словами:

bool b = is_any_of("abc")('a'); // b = true

Это облегчает проверку нескольких разделителей, не требуя самостоятельного написания объекта функции.

4.7. Разбиение строки на лексемы

Проблема