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
—
это шаблон функции, принимающий три аргумента. Он объявлен вот так.
представляют типы результирующей последовательности, входной коллекции и предиката, используемого для определения, является ли очередной объект разделителем. Аргумент последовательности — это последовательность, определенная по стандарту 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. Разбиение строки на лексемы
Проблема
Требуется разбить строку на части, используя набор разделителей.
Решение
Для перебора элементов строки и поиска места нахождения следующих лексем и не-лексем используйте методы