Как токенизировать строку в C ++?

В Java есть удобный метод разделения:

String str = "The quick brown fox";
String[] results = str.split(" ");

Есть ли простой способ сделать это в C ++?


person Bill the Lizard    schedule 10.09.2008    source источник
comment
Не могу поверить, что эта рутинная задача - такая головная боль в C ++   -  person wfbarksdale    schedule 08.09.2011
comment
В C ++ это не головная боль - есть разные способы добиться этого. программисты меньше осведомлены о C ++, чем о C # - речь идет о маркетинге и инвестициях ... см. здесь различные варианты C ++ для достижения того же: cplusplus.com/faq/sequences/strings/split   -  person hB0    schedule 31.10.2013
comment
@ hB0 отвечает на множество вопросов и все еще не решает, что делать - это головная боль. одному нужна эта библиотека, другому - просто пробелы, а третьему пробелы не нужны ..   -  person Paschalis    schedule 14.04.2016
comment
Возможный дубликат Разделить строку в C ++?   -  person KOB    schedule 08.05.2017
comment
Почему в C ++ все должно быть нелегко?   -  person Wael Assaf    schedule 13.08.2019
comment
C ++ - высокопроизводительный язык, поэтому он не способствует универсальному подходу к манипуляциям со строками. Для этого приложения иногда все, что вам нужно, - это последний токен или первые несколько токенов, но разделение будет токенизировать все без каких-либо остановок на основе необходимости. Тем не менее, строковые операции на любом языке обходятся дорого, поэтому стандарт C ++ явно высказался против чрезмерного упрощения интерфейсов, скрывающих настоящую сложность.   -  person John Phu Nguyen    schedule 08.05.2020


Ответы (35)


Алгоритмы стандартной библиотеки C ++ почти всегда основаны на итераторах, а не на конкретных контейнерах. К сожалению, это затрудняет предоставление Java-подобной split функции в стандартной библиотеке C ++, хотя никто не спорит, что это было бы удобно. Но каков будет его возвращаемый тип? std::vector<std::basic_string<…>>? Возможно, но тогда мы вынуждены выполнять (потенциально избыточное и дорогостоящее) распределение.

Вместо этого C ++ предлагает множество способов разделения строк на основе произвольно сложных разделителей, но ни один из них не инкапсулирован так хорошо, как в других языках. Многочисленные способы заполнить сообщения целиком < / а>.

В простейшем случае вы можете выполнять итерацию с помощью std::string::find, пока не нажмете std::string::npos, и извлеките содержимое с помощью std::string::substr.

Более гибкая (и идиоматическая, но базовая) версия для разделения по пробелам будет использовать _6 _ :

auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};

while (iss >> str) {
    process(str);
}

Используя std::istream_iterators, содержимое строкового потока также можно скопировать в вектор с помощью его конструктор диапазона итератора.

Несколько библиотек (например, Boost.Tokenizer) предлагают конкретные токенизаторы.

Более продвинутое разбиение требует регулярных выражений. C ++ предоставляет std::regex_token_iterator для этой цели, в частности:

auto const str = "The quick brown fox"s;
auto const re = std::regex{R"(\s+)"};
auto const vec = std::vector<std::string>(
    std::sregex_token_iterator{begin(str), end(str), re, -1},
    std::sregex_token_iterator{}
);
person Konrad Rudolph    schedule 10.09.2008
comment
К сожалению, ускорение доступно не для всех проектов. Придется искать ответ без повышения. - person FuzzyBunnySlippers; 21.12.2013
comment
Не каждый проект открыт с открытым исходным кодом. Я работаю в строго регулируемых отраслях. На самом деле это не проблема. Это просто факт жизни. Буст доступен не везде. - person FuzzyBunnySlippers; 21.12.2013
comment
@NonlinearIdeas Другой вопрос / ответ был совсем не о проектах с открытым исходным кодом. То же самое верно для любого проекта. Тем не менее, я, конечно, понимаю об ограниченных стандартах, таких как MISRA C, но тогда я понял, что вы все равно создаете все с нуля (если только вам не удастся найти совместимую библиотеку - редкость). В любом случае, суть не в том, что «Boost недоступен» - дело в том, что у вас есть особые требования, для которых почти любой универсальный ответ не подходит. - person Konrad Rudolph; 21.12.2013
comment
@NonlinearIdeas В данном случае другие ответы, не связанные с Boost, также не соответствуют требованиям MISRA. - person Konrad Rudolph; 21.12.2013
comment
Это обсуждение побудило меня спросить о моей конкретной отрасли, вызывающей озабоченность: stackoverflow.com/questions/20714009/. - person FuzzyBunnySlippers; 21.12.2013
comment
@FuzzyBunnySlippers Соревнования по программированию вообще не позволяют использовать библиотеки. - person noɥʇʎԀʎzɐɹƆ; 07.07.2016
comment
Я пытался установить boost 3 раза, каждый раз меня обескураживал STL barf. Это 2016 год, не можем ли мы просто заменить препроцессор C на PHP или JavaScript и встроить его в наши файлы C ++, чтобы мы использовали реальные функции преобразования, которые преобразуют строку в строку и выдают нормальную ошибку, если что-то пойдет не так, вместо архаичного #ifdef #ifndef, которые даже не имеют смысла в C / C ++, поскольку язык хочет быть сжатым до одной строки, а макросы требуют своих собственных строк. Даже Perl в качестве препроцессора был бы лучше, чем использование # ifdefs / # ifndef / template, и это не преобразования в черный ящик. - person Dmitry; 11.09.2016
comment
@Dmitry Что за STL barf ?! И все сообщество очень поддерживает замену препроцессора C - на самом деле, есть предложения сделать это. Но ваше предложение использовать вместо него PHP или какой-либо другой язык было бы огромным шагом назад. - person Konrad Rudolph; 11.09.2016
comment
@KonradRudolph от STL barf В ~ 2-3 экранах расширены шаблоны, которые часто показывают ошибку в коде, которого нет в вашей программе, что затрудняет понимание того, что пошло не так, людям, которые к этому не привыкли. Тем не менее, с тех пор я многому научился, так что, думаю, я снова попробую настроить ускорение, это то, что я смогу настроить. - person Dmitry; 11.09.2016

Класс Boost tokenizer может сделать подобные вещи весьма удобными. просто:

#include <iostream>
#include <string>
#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int, char**)
{
    string text = "token, test   string";

    char_separator<char> sep(", ");
    tokenizer< char_separator<char> > tokens(text, sep);
    BOOST_FOREACH (const string& t, tokens) {
        cout << t << "." << endl;
    }
}

Обновлено для C ++ 11:

#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int, char**)
{
    string text = "token, test   string";

    char_separator<char> sep(", ");
    tokenizer<char_separator<char>> tokens(text, sep);
    for (const auto& t : tokens) {
        cout << t << "." << endl;
    }
}
person Ferruccio    schedule 11.09.2008
comment
Хорошая штука, недавно я этим воспользовался. У моего компилятора Visual Studio есть странное нытье, пока я не использую пробел для разделения двух символов ›перед битом токенов (текст, sep): (ошибка C2947: ожидание« ›» для завершения списка аргументов-шаблонов, найдено »››» ) - person AndyUK; 01.10.2010
comment
@AndyUK да, без пробела компилятор анализирует его как оператор извлечения, а не как два закрывающих шаблона. - person EnabrenTane; 14.06.2011
comment
Теоретически это исправлено в C ++ 0x - person David Souther; 01.09.2011
comment
остерегайтесь третьих параметров конструктора char_separator (по умолчанию drop_empty_tokens, альтернатива keep_empty_tokens). - person Benoit; 17.02.2012
comment
@DavidSouther Говоря о C ++ 0x - BOOST_FOREACH теперь можно заменить новым синтаксисом цикла for. for (строка t: токены) {...} - person Tomas Andrle; 29.02.2012
comment
Двойное ›› не должно быть проблемой для C ++ 11, в этом случае компиляторы VS2010 + содержат ошибку, и вам следует отправить ее при подключении. - person paulm; 22.07.2013
comment
@puk - это широко используемый суффикс для файлов заголовков C ++. (как .h для заголовков C) - person Ferruccio; 13.12.2013

Вот очень простой:

#include <vector>
#include <string>
using namespace std;

vector<string> split(const char *str, char c = ' ')
{
    vector<string> result;

    do
    {
        const char *begin = str;

        while(*str != c && *str)
            str++;

        result.push_back(string(begin, str));
    } while (0 != *str++);

    return result;
}
person Adam Pierce    schedule 10.09.2008
comment
мне нужно добавить прототип этого метода в файл .h? - person Suhrob Samiev; 22.12.2011
comment
Это не совсем лучший ответ, поскольку он по-прежнему использует строковый литерал, который представляет собой простой массив константных символов C. Я полагаю, что спрашивающий спрашивал, может ли он токенизировать строку C ++, которая имеет тип string, введенный последним. - person Vijay Kumar Kanta; 19.04.2017
comment
Здесь нужен новый ответ, потому что я сильно подозреваю, что включение регулярных выражений в C ++ 11 изменило наилучший ответ. - person Omnifarious; 25.10.2017
comment
На этот ответ есть проблема со строками, в которых первый / последний символ равен разделителю. например строка результата - [, a]. - person y30; 13.11.2020

Еще один быстрый способ - использовать getline. Что-то вроде:

stringstream ss("bla bla");
string s;

while (getline(ss, s, ' ')) {
 cout << s << endl;
}

Если хотите, вы можете создать простой split() метод, возвращающий vector<string>, что действительно полезно.

person user35978    schedule 28.11.2008
comment
У меня были проблемы с использованием этой техники с символами 0x0A в строке, из-за чего цикл while завершился преждевременно. В противном случае это хорошее простое и быстрое решение. - person Ryan H.; 25.01.2011
comment
Это хорошо, но помните, что при этом разделитель по умолчанию '\ n' не учитывается. Этот пример будет работать, но если вы используете что-то вроде: while (getline (inFile, word, '')), где inFile - это объект ifstream, содержащий несколько строк, вы получите забавные результаты. - person hackrock; 20.06.2012
comment
Жаль, что getline возвращает поток, а не строку, что делает его непригодным для использования в списках инициализации без временного хранилища - person fuzzyTew; 03.08.2013
comment
Прохладный! Никакого ускорения и C ++ 11, хорошее решение для старых проектов! - person Deqing; 30.04.2014
comment
ЭТО и ответ, название функции немного неудобно. - person Nils; 02.06.2019

Используйте strtok. На мой взгляд, нет необходимости создавать класс вокруг токенизации, если strtok не предоставит вам то, что вам нужно. Возможно, и нет, но за 15 с лишним лет написания различного кода синтаксического анализа на C и C ++ я всегда использовал strtok. Вот пример

char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
    printf ("Token: %s\n", p);
    p = strtok(NULL, " ");
}

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

На мой взгляд, приведенный выше код намного проще и проще в использовании, чем писать для него отдельный класс. Для меня это одна из тех функций, которые предоставляет язык, и он делает это хорошо и чисто. Это просто решение на основе C. Это уместно, это просто, и вам не нужно писать много лишнего кода :-)

person Mark    schedule 10.09.2008
comment
Не то чтобы мне не нравился C, однако strtok не является потокобезопасным, и вы должны быть уверены, что отправляемая строка содержит нулевой символ, чтобы избежать возможного переполнения буфера. - person tloach; 10.05.2010
comment
Есть strtok_r, но это вопрос C ++. - person Prof. Falken; 06.10.2010
comment
@tloach: в компиляторе MS C ++ strtok является потокобезопасным, поскольку внутренняя статическая переменная создается в TLS (локальное хранилище потока) (на самом деле это зависит от компилятора) - person Ahmed Said; 28.11.2010
comment
вы можете проверить это nibuthomas.wordpress.com/2008/06 / 25 / - person Ahmed Said; 28.11.2010
comment
@ahmed: потокобезопасность означает больше, чем просто возможность дважды запустить функцию в разных потоках. В этом случае, если поток изменяется во время выполнения strtok, можно сделать так, чтобы строка была действительной в течение всего запуска strtok, но strtok все равно будет ошибаться, потому что строка изменилась, теперь она уже прошла нулевой символ и собирается продолжайте читать память, пока она не обнаружит нарушение безопасности или не найдет нулевой символ. Это проблема исходных строковых функций C, если вы не укажете длину где-нибудь, у вас возникнут проблемы. - person tloach; 29.11.2010
comment
strtok требует указателя на неконстантный массив char с завершающим нулем, что не является обычным явлением, которое можно найти в коде C ++ ... какой ваш любимый способ преобразовать в это из std :: string? - person fuzzyTew; 03.08.2013
comment
@tloach: при каких обстоятельствах строка не будет содержать нулевой символ? Я только знаю, что иногда к строкам добавляется '\ 0' ... (вопрос нуба, я знаю!) - person The Chaz 2.0; 18.09.2013
comment
Это хорошее решение и хорошо подходит для проектов C. Однако он несовместим с литералами или объектами строкового типа C ++. Похоже, C ++ требует класса для всего. ›_‹ - person Vijay Kumar Kanta; 04.11.2016
comment
strtok_r является потокобезопасным, и его лучше использовать, когда он доступен. Просто попробуйте, чтобы узнать, поддерживает ли это ваш компилятор. Наверное, да. - person Gabriel Staples; 25.09.2019
comment
@fuzzyTew Я не сторонник использования strtok в коде C ++. Однако, когда мне нужно было преобразовать устаревший код, используя его, чтобы избежать голых указателей (и отсутствие у них не-RAII гарантий того, что то, на что они указывают, будет освобождено, если будет выбрано исключение), я использовал vector<char> foo(str.begin(), str.end()+1); char *p = strtok(foo.data(), " "); Таким образом, вектор владеет доступная для записи копия данных строки и освободит ее в случае возникновения исключения. (+1 означает, что нулевой признак конца строки скопирован в вектор.) - person Some Guy; 03.07.2020

Вы можете использовать потоки, итераторы и алгоритм копирования, чтобы делать это напрямую.

#include <string>
#include <vector>
#include <iostream>
#include <istream>
#include <ostream>
#include <iterator>
#include <sstream>
#include <algorithm>

int main()
{
  std::string str = "The quick brown fox";

  // construct a stream from the string
  std::stringstream strstr(str);

  // use stream iterators to copy the stream to the vector as whitespace separated strings
  std::istream_iterator<std::string> it(strstr);
  std::istream_iterator<std::string> end;
  std::vector<std::string> results(it, end);

  // send the vector to stdout.
  std::ostream_iterator<std::string> oit(std::cout);
  std::copy(results.begin(), results.end(), oit);
}
person KeithB    schedule 10.09.2008
comment
Я нахожу эти std :: раздражающим при чтении .. почему бы не использовать using? - person user35978; 28.11.2008
comment
@Vadi: потому что редактирование чужого поста довольно навязчиво. @pheze: Я предпочитаю, чтобы std так я знал, откуда взялся мой объект, это просто вопрос стиля. - person Matthieu M.; 02.04.2010
comment
Я понимаю вашу причину и думаю, что это действительно хороший выбор, если он работает для вас, но с педагогической точки зрения я действительно согласен с pheze. Легче читать и понимать совершенно чужой пример, такой как этот, с использованием пространства имен std вверху, потому что он требует меньше усилий для интерпретации следующих строк ... особенно в этом случае, потому что все взято из стандартной библиотеки. Вы можете сделать его легко читаемым и очевидным, откуда берутся объекты, с помощью нескольких последовательностей использования std :: string; и т.д. Тем более, что функция такая короткая. - person cheshirekow; 16.07.2010
comment
Честно говоря, функционально это было бы намного удобнее. А эти std :: просто уродливы. Я отредактировал, но не ожидаю, что он появится где-нибудь в ближайшем будущем. Тем не менее попробую. - person Arnthor; 16.10.2011
comment
user - потому что кто-то может скопировать этот пример, и мы бы не хотели, чтобы они использовали 'using' :) - person Good Person; 31.01.2012
comment
Несмотря на то, что префиксы std :: раздражают или уродливы, лучше всего включить их в пример кода, чтобы было совершенно ясно, откуда берутся эти функции. Если они вас беспокоят, легко заменить их на using после того, как вы украдете пример и заявите его как свой собственный. - person dlchambers; 11.04.2012
comment
Ага! что он сказал! рекомендуется использовать префикс std. Любая большая база кода, без сомнения, будет иметь свои собственные библиотеки и пространства имен, а использование пространства имен std вызовет у вас головную боль, когда вы начнете вызывать конфликты пространств имен. - person Miek; 18.07.2012
comment
Это очень умно, но как я мог разделить строку, скажем, запятой, а не пробелом или пробелом единственным разделителем - я спрашиваю, потому что мой быстрый поиск в Интернете о том, как это работает, не упомянул, как разделяются строки. - person Skizz; 15.04.2015
comment
Не могли бы вы обновить свой ответ для std::wstrings? Кажется, я слишком тупой, чтобы заставить его компилировать :( - person YePhIcK; 06.11.2015
comment
Вам не нужно #include <istream> или <ostream>. - person Martin Broadhurst; 09.02.2016
comment
@Miek Особенно при использовании Boost и стандартной библиотеки (<regex>), которая может быть boost::regex или std::regex - person Nubcake; 13.08.2017

Решение с использованием regex_token_iterators:

#include <iostream>
#include <regex>
#include <string>

using namespace std;

int main()
{
    string str("The quick brown fox");

    regex reg("\\s+");

    sregex_token_iterator iter(str.begin(), str.end(), reg, -1);
    sregex_token_iterator end;

    vector<string> vec(iter, end);

    for (auto a : vec)
    {
        cout << a << endl;
    }
}
person w.b    schedule 14.12.2014
comment
Это должен быть самый высокий рейтинг. Это правильный способ сделать это в C ++ ›= 11. - person Omnifarious; 25.10.2017
comment
Я рад, что дошел до этого ответа (на данный момент было только 9 голосов). Именно так должен выглядеть код C ++ 11 для этой задачи! - person YePhIcK; 07.01.2018
comment
Отличный ответ, который не полагается на внешние библиотеки и использует уже доступные библиотеки. - person Andrew; 06.02.2018
comment
Отличный ответ, обеспечивающий максимальную гибкость в разделителях. Несколько предостережений: использование \ s + regex позволяет избежать пустых токенов в середине текста, но дает пустой первый токен, если текст начинается с пробела. Кроме того, регулярное выражение кажется медленным: на моем ноутбуке для 20 МБ случайного текста требуется 0,6 секунды, по сравнению с 0,014 секунды для strtok, strsep или ответа Пархема с использованием str.find_first_of, или 0,027 секунды для Perl или 0,021 секунды для Python. . Для короткого текста скорость может не иметь значения. - person Mark Gates; 21.04.2018
comment
Это круто. Спасибо, что поделился. - person Vijay Rajanna; 19.09.2018
comment
Хорошо, возможно, это выглядит круто, но это явно злоупотребление регулярными выражениями. Разумно, только если вам наплевать на производительность. - person Marek R; 18.07.2019

Не обижайтесь, ребята, но для такой простой задачи вы делаете вещи способом слишком сложными. Есть много причин использовать Boost. Но для чего-то такого простого, это все равно, что сбить муху сани 20 #.

void
split( vector<string> & theStringVector,  /* Altered/returned value */
       const  string  & theString,
       const  string  & theDelimiter)
{
    UASSERT( theDelimiter.size(), >, 0); // My own ASSERT macro.

    size_t  start = 0, end = 0;

    while ( end != string::npos)
    {
        end = theString.find( theDelimiter, start);

        // If at end, use length=maxLength.  Else use length=end-start.
        theStringVector.push_back( theString.substr( start,
                       (end == string::npos) ? string::npos : end - start));

        // If at end, use start=maxSize.  Else use start=end+delimiter.
        start = (   ( end > (string::npos - theDelimiter.size()) )
                  ?  string::npos  :  end + theDelimiter.size());
    }
}

Например (для случая Дуга),

#define SHOW(I,X)   cout << "[" << (I) << "]\t " # X " = \"" << (X) << "\"" << endl

int
main()
{
    vector<string> v;

    split( v, "A:PEP:909:Inventory Item", ":" );

    for (unsigned int i = 0;  i < v.size();   i++)
        SHOW( i, v[i] );
}

И да, мы могли бы использовать split (), чтобы вернуть новый вектор, а не передавать его. Это тривиально обернуть и перегрузить. Но в зависимости от того, что я делаю, я часто считаю, что лучше повторно использовать уже существующие объекты, чем всегда создавать новые. (Пока я не забываю очищать вектор между ними!)

Ссылка: http://www.cplusplus.com/reference/string/string/.

(Первоначально я писал ответ на вопрос Дуга: Изменение и извлечение строк C ++ на основе разделителей (закрыто). Но поскольку Мартин Йорк закрыл этот вопрос указателем здесь ... я просто обобщу свой код.)

person Mr.Ree    schedule 28.11.2008
comment
Зачем определять макрос, который вы используете только в одном месте. И чем ваш UASSERT лучше стандартного assert. Такое разделение сравнения на 3 токена не делает ничего, кроме как требует больше запятых, чем вам могло бы потребоваться. - person crelbor; 13.05.2011
comment
Может быть, макрос UASSERT показывает (в сообщении об ошибке) фактическую взаимосвязь между (и значениями) двух сравниваемых значений? ИМХО, это действительно неплохая идея. - person GhassanPL; 17.03.2012
comment
Ух, а почему класс std::string не включает функцию split ()? - person Mr. Shickadance; 19.04.2012
comment
Я думаю, что последняя строка в цикле while должна быть start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());, а цикл while - while (start != string::npos). Кроме того, я проверяю подстроку, чтобы убедиться, что она не пуста, прежде чем вставлять ее в вектор. - person John K; 01.08.2012
comment
@JohnK Если входные данные имеют два последовательных разделителя, очевидно, что строка между ними пуста и должна быть вставлена ​​в вектор. Если пустые значения неприемлемы для конкретной цели, это другое дело, но ИМХО такие ограничения должны применяться за пределами этого вида функций очень общего назначения. - person Lauri Nurmi; 26.06.2013
comment
Почему бы не разрешить также пустую строку в качестве разделителя? - person ; 13.08.2017

Boost имеет сильную функцию разделения: boost :: algorithm :: split.

Пример программы:

#include <vector>
#include <boost/algorithm/string.hpp>

int main() {
    auto s = "a,b, c ,,e,f,";
    std::vector<std::string> fields;
    boost::split(fields, s, boost::is_any_of(","));
    for (const auto& field : fields)
        std::cout << "\"" << field << "\"\n";
    return 0;
}

Выход:

"a"
"b"
" c "
""
"e"
"f"
""
person Raz    schedule 12.09.2008

Я знаю, что вы просили решение на C ++, но вы можете счесть это полезным:

Qt

#include <QString>

...

QString str = "The quick brown fox"; 
QStringList results = str.split(" "); 

Преимущество перед Boost в этом примере заключается в том, что это прямое сопоставление с кодом вашего сообщения.

См. Дополнительную информацию в документации Qt.

person sivabudh    schedule 04.08.2010

Это простое решение только для STL (~ 5 строк!) С использованием std::find и std::find_first_not_of, которое обрабатывает повторы разделителя (например, пробелы или точки), а также начальные и конечные разделители:

#include <string>
#include <vector>

void tokenize(std::string str, std::vector<string> &token_v){
    size_t start = str.find_first_not_of(DELIMITER), end=start;

    while (start != std::string::npos){
        // Find next occurence of delimiter
        end = str.find(DELIMITER, start);
        // Push back the token found into vector
        token_v.push_back(str.substr(start, end-start));
        // Skip all occurences of the delimiter to find new start
        start = str.find_first_not_of(DELIMITER, end);
    }
}

Попробуйте вживую!

person Parham    schedule 28.02.2015
comment
Это хороший вариант, но я думаю, вам нужно использовать find_first_of () вместо find (), чтобы он работал правильно с несколькими разделителями. - person ; 27.11.2015
comment
@ user755921 несколько разделителей пропускаются при нахождении начальной позиции с помощью find_first_not_of. - person Beginner; 05.10.2018

Вот образец класса токенизатора, который может делать то, что вы хотите

//Header file
class Tokenizer 
{
    public:
        static const std::string DELIMITERS;
        Tokenizer(const std::string& str);
        Tokenizer(const std::string& str, const std::string& delimiters);
        bool NextToken();
        bool NextToken(const std::string& delimiters);
        const std::string GetToken() const;
        void Reset();
    protected:
        size_t m_offset;
        const std::string m_string;
        std::string m_token;
        std::string m_delimiters;
};

//CPP file
const std::string Tokenizer::DELIMITERS(" \t\n\r");

Tokenizer::Tokenizer(const std::string& s) :
    m_string(s), 
    m_offset(0), 
    m_delimiters(DELIMITERS) {}

Tokenizer::Tokenizer(const std::string& s, const std::string& delimiters) :
    m_string(s), 
    m_offset(0), 
    m_delimiters(delimiters) {}

bool Tokenizer::NextToken() 
{
    return NextToken(m_delimiters);
}

bool Tokenizer::NextToken(const std::string& delimiters) 
{
    size_t i = m_string.find_first_not_of(delimiters, m_offset);
    if (std::string::npos == i) 
    {
        m_offset = m_string.length();
        return false;
    }

    size_t j = m_string.find_first_of(delimiters, i);
    if (std::string::npos == j) 
    {
        m_token = m_string.substr(i);
        m_offset = m_string.length();
        return true;
    }

    m_token = m_string.substr(i, j - i);
    m_offset = j;
    return true;
}

Пример:

std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
    v.push_back(s.GetToken());
}
person vzczc    schedule 10.09.2008

pystring - это небольшая библиотека, которая реализует набор строковых функций Python, включая метод разделения:

#include <string>
#include <vector>
#include "pystring.h"

std::vector<std::string> chunks;
pystring::split("this string", chunks);

// also can specify a separator
pystring::split("this-string", chunks, "-");
person dbr    schedule 29.12.2011
comment
Вау, вы ответили на мой вопрос, а также на многие вопросы в будущем. Я понимаю, что c ++ - мощный инструмент. Но когда разбиение строки приводит к исходному коду, подобному приведенным выше ответам, это явно разочаровывает. Мне бы хотелось узнать о других подобных библиотеках, которые снижают удобство использования языков более высокого уровня. - person Ross; 15.06.2012
comment
вау, серьезно вы только что сделали мой день !! не знал о pystring. это сэкономит мне много времени! - person accraze; 10.02.2015

Я отправил этот ответ на аналогичный вопрос.
Не изобретайте велосипед. Я использовал несколько библиотек, и самая быстрая и гибкая, с которой я столкнулся, это: C ++ String Toolkit Библиотека.

Вот пример его использования, который я опубликовал в другом месте в stackoverflow.

#include <iostream>
#include <vector>
#include <string>
#include <strtk.hpp>

const char *whitespace  = " \t\r\n\f";
const char *whitespace_and_punctuation  = " \t\r\n\f;,=";

int main()
{
    {   // normal parsing of a string into a vector of strings
       std::string s("Somewhere down the road");
       std::vector<std::string> result;
       if( strtk::parse( s, whitespace, result ) )
       {
           for(size_t i = 0; i < result.size(); ++i )
            std::cout << result[i] << std::endl;
       }
    }

    {  // parsing a string into a vector of floats with other separators
       // besides spaces

       std::string s("3.0, 3.14; 4.0");
       std::vector<float> values;
       if( strtk::parse( s, whitespace_and_punctuation, values ) )
       {
           for(size_t i = 0; i < values.size(); ++i )
            std::cout << values[i] << std::endl;
       }
    }

    {  // parsing a string into specific variables

       std::string s("angle = 45; radius = 9.9");
       std::string w1, w2;
       float v1, v2;
       if( strtk::parse( s, whitespace_and_punctuation, w1, v1, w2, v2) )
       {
           std::cout << "word " << w1 << ", value " << v1 << std::endl;
           std::cout << "word " << w2 << ", value " << v2 << std::endl;
       }
    }

    return 0;
}
person DannyK    schedule 07.01.2014

Посмотрите этот пример. Это может вам помочь ..

#include <iostream>
#include <sstream>

using namespace std;

int main ()
{
    string tmps;
    istringstream is ("the dellimiter is the space");
    while (is.good ()) {
        is >> tmps;
        cout << tmps << "\n";
    }
    return 0;
}
person sohesado    schedule 20.12.2010
comment
Я бы сделал while ( is >> tmps ) { std::cout << tmps << "\n"; } - person jordix; 05.04.2016

Ответ Адама Пирса предоставляет созданный вручную токенизатор, принимающий const char*. С итераторами работать немного сложнее, потому что приращение конечного итератора string не определено. Тем не менее, учитывая string str{ "The quick brown fox" }, мы определенно можем добиться этого:

auto start = find(cbegin(str), cend(str), ' ');
vector<string> tokens{ string(cbegin(str), start) };

while (start != cend(str)) {
    const auto finish = find(++start, cend(str), ' ');

    tokens.push_back(string(start, finish));
    start = finish;
}

Живой пример


Если вы стремитесь к абстрактной сложности с помощью стандартных функций, как On Freund предлагает _ 5_ - простой вариант:

vector<string> tokens;

for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);

Если у вас нет доступа к C ++ 17, вам необходимо заменить data(str), как в этом примере: http://ideone.com/8kAGoa

Хотя это не показано в примере, strtok не обязательно использовать один и тот же разделитель для каждого токена. Однако наряду с этим преимуществом есть несколько недостатков:

  1. strtok нельзя использовать на нескольких strings одновременно: либо необходимо передать nullptr для продолжения токенизации текущего string, либо необходимо передать новый char* для токенизации (однако существуют некоторые нестандартные реализации, которые поддерживают это, например: strtok_s)
  2. По той же причине strtok нельзя использовать одновременно в нескольких потоках (однако это может быть определено реализацией, например: Реализация Visual Studio ориентирована на многопотоковое исполнение)
  3. Вызов strtok изменяет string, с которым он работает, поэтому его нельзя использовать для const string, const char* или буквальных строк, для токенизации любого из них с помощью strtok или для работы с string, содержимое которого необходимо сохранить, str придется скопировать , то с копией можно работать

c ++ 20 предоставляет нам split_view для разметки строк неразрушающим способом: https://topanswers.xyz/cplusplus?q=749#a874


Предыдущие методы не могут генерировать токенизированные vector на месте, то есть без их абстрагирования во вспомогательную функцию они не могут инициализировать const vector<string> tokens. Эту функциональность и возможность принимать любой разделитель пробелов можно использовать с помощью _ 26_. Например, дано: const string str{ "The quick \tbrown \nfox" }, мы можем сделать это:

istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };

Живой пример

Требуемая конструкция istringstream для этого варианта имеет гораздо большую стоимость, чем предыдущие 2 варианта, однако эта стоимость обычно скрывается за счет string распределения.


Если ни один из вышеперечисленных вариантов не является достаточно гибким для ваших нужд токенизации, наиболее гибким вариантом является использование regex_token_iterator Конечно, такая гибкость влечет за собой большие расходы, но, опять же, это, вероятно, скрыто в string стоимости размещения. Скажем, например, мы хотим токенизировать на основе запятых без экранирования, а также использовать пробелы, учитывая следующий ввод: const string str{ "The ,qu\\,ick ,\tbrown, fox" } мы можем сделать это:

const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };

Живой пример

person Jonathan Mee    schedule 26.07.2016
comment
strtok_s - это, кстати, стандарт C11. strtok_r - это стандарт POSIX2001. Между ними есть стандартная реентерабельная версия strtok для большинства платформ. - person Andon M. Coleman; 14.12.2016
comment
@ AndonM.Coleman Но это вопрос c ++, а в C ++ _ 1_ включает только c99 версия strtok. Итак, я предполагаю, что вы просто предоставляете этот комментарий в качестве вспомогательного материала, демонстрируя доступность strtok расширений для конкретной реализации? - person Jonathan Mee; 14.12.2016
comment
Просто это не так уж и нестандартно, как люди могли бы подумать. strtok_s предоставляется как C11, так и как отдельное расширение в среде выполнения Microsoft C. Здесь есть любопытная история, когда _s функции Microsoft стали стандартом C. - person Andon M. Coleman; 14.12.2016
comment
@ AndonM.Coleman Верно, я с тобой. Очевидно, что если это в стандарте C11, на интерфейс и реализацию накладываются ограничения, которые требуют идентичного поведения независимо от платформы. Теперь единственная проблема - обеспечить доступность функции C11 для всех платформ. Надеюсь, что стандарт C11 будет чем-то, что C ++ 17 или C ++ 20 выберет для использования. - person Jonathan Mee; 14.12.2016

MFC / ATL имеет очень хороший токенизатор. Из MSDN:

CAtlString str( "%First Second#Third" );
CAtlString resToken;
int curPos= 0;

resToken= str.Tokenize("% #",curPos);
while (resToken != "")
{
   printf("Resulting token: %s\n", resToken);
   resToken= str.Tokenize("% #",curPos);
};

Output

Resulting Token: First
Resulting Token: Second
Resulting Token: Third
person Jim In Texas    schedule 22.03.2009
comment
Эта функция Tokenize () пропускает пустые токены, например, если в основной строке есть подстрока %%, пустой токен не возвращается. Это пропущено. - person Sheen; 20.01.2011

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

person On Freund    schedule 10.09.2008
comment
Обратите внимание, что strtok изменяет строку, которую вы проверяете, поэтому вы не можете использовать ее в строках const char * без создания копии. - person Graeme Perrow; 10.09.2008
comment
Проблема многопоточности заключается в том, что strtok использует глобальную переменную, чтобы отслеживать, где она находится, поэтому, если у вас есть два потока, каждый из которых использует strtok, вы получите неопределенное поведение. - person JohnMcG; 10.09.2008
comment
@JohnMcG Или просто используйте strtok_s, что в основном strtok с явной передачей состояния . - person Matthias; 02.06.2018

Для простых вещей я просто использую следующее:

unsigned TokenizeString(const std::string& i_source,
                        const std::string& i_seperators,
                        bool i_discard_empty_tokens,
                        std::vector<std::string>& o_tokens)
{
    unsigned prev_pos = 0;
    unsigned pos = 0;
    unsigned number_of_tokens = 0;
    o_tokens.clear();
    pos = i_source.find_first_of(i_seperators, pos);
    while (pos != std::string::npos)
    {
        std::string token = i_source.substr(prev_pos, pos - prev_pos);
        if (!i_discard_empty_tokens || token != "")
        {
            o_tokens.push_back(i_source.substr(prev_pos, pos - prev_pos));
            number_of_tokens++;
        }

        pos++;
        prev_pos = pos;
        pos = i_source.find_first_of(i_seperators, pos);
    }

    if (prev_pos < i_source.length())
    {
        o_tokens.push_back(i_source.substr(prev_pos));
        number_of_tokens++;
    }

    return number_of_tokens;
}

Трусливый отказ от ответственности: я пишу программное обеспечение для обработки данных в реальном времени, где данные поступают через двоичные файлы, сокеты или некоторые вызовы API (карты ввода-вывода, камеры). Я никогда не использую эту функцию для чего-то более сложного или критичного по времени, чем чтение внешних файлов конфигурации при запуске.

person jilles de wit    schedule 15.09.2008

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

Используйте выражение (\ w +) и переменную в \ 1 (или $ 1 в зависимости от реализации регулярных выражений в библиотеке).

person Fawix    schedule 22.04.2011
comment
+1 за предложение регулярного выражения, если вам не нужна скорость деформации, это наиболее гибкое решение, которое еще не везде поддерживается, но со временем это станет менее важным. - person odinthenerd; 27.07.2014
comment
+1 от меня, только что попробовал ‹regex› в c ++ 11. Так просто и элегантно - person StahlRat; 06.11.2014

Здесь много слишком сложных предложений. Попробуйте это простое решение std :: string:

using namespace std;

string someText = ...

string::size_type tokenOff = 0, sepOff = tokenOff;
while (sepOff != string::npos)
{
    sepOff = someText.find(' ', sepOff);
    string::size_type tokenLen = (sepOff == string::npos) ? sepOff : sepOff++ - tokenOff;
    string token = someText.substr(tokenOff, tokenLen);
    if (!token.empty())
        /* do something with token */;
    tokenOff = sepOff;
}
person David919    schedule 01.08.2012

Я думал, что для этого нужен оператор >> в строковых потоках:

string word; sin >> word;
person Daren Thomas    schedule 10.09.2008
comment
Моя вина, что я привел плохой (слишком простой) пример. Насколько я знаю, это работает только тогда, когда ваш разделитель - пробел. - person Bill the Lizard; 25.11.2008

Вот подход, который позволяет вам контролировать, включены ли пустые токены (например, strsep) или исключены (например, strtok).

#include <string.h> // for strchr and strlen

/*
 * want_empty_tokens==true  : include empty tokens, like strsep()
 * want_empty_tokens==false : exclude empty tokens, like strtok()
 */
std::vector<std::string> tokenize(const char* src,
                                  char delim,
                                  bool want_empty_tokens)
{
  std::vector<std::string> tokens;

  if (src and *src != '\0') // defensive
    while( true )  {
      const char* d = strchr(src, delim);
      size_t len = (d)? d-src : strlen(src);

      if (len or want_empty_tokens)
        tokens.push_back( std::string(src, len) ); // capture token

      if (d) src += len+1; else break;
    }

  return tokens;
}
person Darren Smith    schedule 26.10.2012

Мне кажется странным, что, несмотря на то, что все мы, ботаники, осознающие скорость, здесь, на SO, никто не представил версию, которая использует сгенерированную во время компиляции таблицу поиска для разделителя (пример реализации ниже). Использование таблицы поиска и итераторов должно превзойти std :: regex по эффективности, если вам не нужно превзойти регулярное выражение, просто используйте его, его стандарт C ++ 11 и сверхгибкий.

Некоторые уже предложили регулярное выражение, но для новичков вот упакованный пример, который должен делать именно то, что ожидает OP:

std::vector<std::string> split(std::string::const_iterator it, std::string::const_iterator end, std::regex e = std::regex{"\\w+"}){
    std::smatch m{};
    std::vector<std::string> ret{};
    while (std::regex_search (it,end,m,e)) {
        ret.emplace_back(m.str());              
        std::advance(it, m.position() + m.length()); //next start position = match position + match length
    }
    return ret;
}
std::vector<std::string> split(const std::string &s, std::regex e = std::regex{"\\w+"}){  //comfort version calls flexible version
    return split(s.cbegin(), s.cend(), std::move(e));
}
int main ()
{
    std::string str {"Some people, excluding those present, have been compile time constants - since puberty."};
    auto v = split(str);
    for(const auto&s:v){
        std::cout << s << std::endl;
    }
    std::cout << "crazy version:" << std::endl;
    v = split(str, std::regex{"[^e]+"});  //using e as delim shows flexibility
    for(const auto&s:v){
        std::cout << s << std::endl;
    }
    return 0;
}

Если нам нужно быть быстрее и принять ограничение, что все символы должны быть 8-битными, мы можем создать таблицу поиска во время компиляции, используя метапрограммирование:

template<bool...> struct BoolSequence{};        //just here to hold bools
template<char...> struct CharSequence{};        //just here to hold chars
template<typename T, char C> struct Contains;   //generic
template<char First, char... Cs, char Match>    //not first specialization
struct Contains<CharSequence<First, Cs...>,Match> :
    Contains<CharSequence<Cs...>, Match>{};     //strip first and increase index
template<char First, char... Cs>                //is first specialization
struct Contains<CharSequence<First, Cs...>,First>: std::true_type {}; 
template<char Match>                            //not found specialization
struct Contains<CharSequence<>,Match>: std::false_type{};

template<int I, typename T, typename U> 
struct MakeSequence;                            //generic
template<int I, bool... Bs, typename U> 
struct MakeSequence<I,BoolSequence<Bs...>, U>:  //not last
    MakeSequence<I-1, BoolSequence<Contains<U,I-1>::value,Bs...>, U>{};
template<bool... Bs, typename U> 
struct MakeSequence<0,BoolSequence<Bs...>,U>{   //last  
    using Type = BoolSequence<Bs...>;
};
template<typename T> struct BoolASCIITable;
template<bool... Bs> struct BoolASCIITable<BoolSequence<Bs...>>{
    /* could be made constexpr but not yet supported by MSVC */
    static bool isDelim(const char c){
        static const bool table[256] = {Bs...};
        return table[static_cast<int>(c)];
    }   
};
using Delims = CharSequence<'.',',',' ',':','\n'>;  //list your custom delimiters here
using Table = BoolASCIITable<typename MakeSequence<256,BoolSequence<>,Delims>::Type>;

С этим легко сделать getNextToken функцию:

template<typename T_It>
std::pair<T_It,T_It> getNextToken(T_It begin,T_It end){
    begin = std::find_if(begin,end,std::not1(Table{})); //find first non delim or end
    auto second = std::find_if(begin,end,Table{});      //find first delim or end
    return std::make_pair(begin,second);
}

Пользоваться им также просто:

int main() {
    std::string s{"Some people, excluding those present, have been compile time constants - since puberty."};
    auto it = std::begin(s);
    auto end = std::end(s);
    while(it != std::end(s)){
        auto token = getNextToken(it,end);
        std::cout << std::string(token.first,token.second) << std::endl;
        it = token.second;
    }
    return 0;
}

Вот живой пример: http://ideone.com/GKtkLQ

person odinthenerd    schedule 26.07.2014
comment
Можно ли токенизировать с помощью разделителя String? - person Galigator; 26.07.2014
comment
эта версия оптимизирована только для односимвольных разделителей, использование таблицы поиска не подходит для многосимвольных (строковых) разделителей, поэтому по эффективности сложнее превзойти регулярное выражение. - person odinthenerd; 27.07.2014

Я знаю, что на этот вопрос уже дан ответ, но я хочу внести свой вклад. Может быть, мое решение немного простое, но вот что я придумал:

vector<string> get_words(string const& text, string const& separator)
{
    vector<string> result;
    string tmp = text;

    size_t first_pos = 0;
    size_t second_pos = tmp.find(separator);

    while (second_pos != string::npos)
    {
        if (first_pos != second_pos)
        {
            string word = tmp.substr(first_pos, second_pos - first_pos);
            result.push_back(word);
        }
        tmp = tmp.substr(second_pos + separator.length());
        second_pos = tmp.find(separator);
    }

    result.push_back(tmp);

    return result;
}

Прокомментируйте, пожалуйста, есть ли в моем коде лучший подход к чему-то или что-то не так.

ОБНОВЛЕНИЕ: добавлен общий разделитель

person NutCracker    schedule 09.05.2018
comment
Использовал ваше решение из толпы :) Могу ли я изменить ваш код, чтобы добавить какой-либо разделитель? - person Zac; 21.11.2019
comment
@Zac рад, что вам понравилось, и, конечно, вы можете его изменить ... просто добавьте раздел обновлений жирным шрифтом к моему ответу ... - person NutCracker; 21.11.2019

Если вы используете диапазоны C ++ - полную библиотеку range-v3, а не ограниченную функциональность принято в C ++ 20 - это можно сделать так:

auto results = str | ranges::views::tokenize(" ",1);

... и это вычисляется лениво, то есть O (1) время и пространство. В качестве альтернативы вы можете установить вектор в этот диапазон:

auto results = str | ranges::views::tokenize(" ",1) | ranges::to<std::vector>();

это займет O (m) пробела и O (n) времени, если str имеет n символов, составляющих m слов.

См. Также пример токенизации библиотеки, здесь.

person einpoklum    schedule 15.08.2020

вы можете воспользоваться boost :: make_find_iterator. Что-то похожее на это:

template<typename CH>
inline vector< basic_string<CH> > tokenize(
    const basic_string<CH> &Input,
    const basic_string<CH> &Delimiter,
    bool remove_empty_token
    ) {

    typedef typename basic_string<CH>::const_iterator string_iterator_t;
    typedef boost::find_iterator< string_iterator_t > string_find_iterator_t;

    vector< basic_string<CH> > Result;
    string_iterator_t it = Input.begin();
    string_iterator_t it_end = Input.end();
    for(string_find_iterator_t i = boost::make_find_iterator(Input, boost::first_finder(Delimiter, boost::is_equal()));
        i != string_find_iterator_t();
        ++i) {
        if(remove_empty_token){
            if(it != i->begin())
                Result.push_back(basic_string<CH>(it,i->begin()));
        }
        else
            Result.push_back(basic_string<CH>(it,i->begin()));
        it = i->end();
    }
    if(it != it_end)
        Result.push_back(basic_string<CH>(it,it_end));

    return Result;
}
person Arash    schedule 03.08.2011

Вот мой Swiss® Army Knife со строковыми токенизаторами для разделения строк по пробелам, учета строк, заключенных в одинарные и двойные кавычки, а также удаления этих символов из результатов. Я использовал RegexBuddy 4.x для создания большей части фрагмента кода, но я добавил настраиваемую обработку для удаления кавычек и некоторых других вещей.

#include <string>
#include <locale>
#include <regex>

std::vector<std::wstring> tokenize_string(std::wstring string_to_tokenize) {
    std::vector<std::wstring> tokens;

    std::wregex re(LR"(("[^"]*"|'[^']*'|[^"' ]+))", std::regex_constants::collate);

    std::wsregex_iterator next( string_to_tokenize.begin(),
                                string_to_tokenize.end(),
                                re,
                                std::regex_constants::match_not_null );

    std::wsregex_iterator end;
    const wchar_t single_quote = L'\'';
    const wchar_t double_quote = L'\"';
    while ( next != end ) {
        std::wsmatch match = *next;
        const std::wstring token = match.str( 0 );
        next++;

        if (token.length() > 2 && (token.front() == double_quote || token.front() == single_quote))
            tokens.emplace_back( std::wstring(token.begin()+1, token.begin()+token.length()-1) );
        else
            tokens.emplace_back(token);
    }
    return tokens;
}
person kayleeFrye_onDeck    schedule 21.09.2018
comment
Голоса (против) могут быть такими же конструктивными, как и голоса за, но только не тогда, когда вы не оставляете комментарии о том, почему ... - person kayleeFrye_onDeck; 09.08.2019
comment
Я уравнял вас, но это может быть потому, что код выглядит довольно устрашающе для программиста, ищущего в Google «как разбить строку», особенно без документации. - person mattshu; 12.01.2020
comment
Спасибо @mattshu! Сегменты регулярных выражений делают его сложным или что-то еще? - person kayleeFrye_onDeck; 16.01.2020

Если известна максимальная длина токенизируемой входной строки, можно воспользоваться этим и реализовать очень быструю версию. Ниже я набросал основную идею, которая была вдохновлена ​​как strtok (), так и структурой данных «суффиксный массив», описанной Джоном Бентли «Программирование Perls», 2-е издание, глава 15. Класс C ++ в этом случае дает лишь некоторую организацию и удобство. использования. Показанная реализация может быть легко расширена для удаления начальных и конечных пробелов в токенах.

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

Заголовочный файл:

class TextLineSplitter
{
public:

    TextLineSplitter( const size_t max_line_len );

    ~TextLineSplitter();

    void            SplitLine( const char *line,
                               const char sep_char = ',',
                             );

    inline size_t   NumTokens( void ) const
    {
        return mNumTokens;
    }

    const char *    GetToken( const size_t token_idx ) const
    {
        assert( token_idx < mNumTokens );
        return mTokens[ token_idx ];
    }

private:
    const size_t    mStorageSize;

    char           *mBuff;
    char          **mTokens;
    size_t          mNumTokens;

    inline void     ResetContent( void )
    {
        memset( mBuff, 0, mStorageSize );
        // mark all items as empty:
        memset( mTokens, 0, mStorageSize * sizeof( char* ) );
        // reset counter for found items:
        mNumTokens = 0L;
    }
};

Файл реализации:

TextLineSplitter::TextLineSplitter( const size_t max_line_len ):
    mStorageSize ( max_line_len + 1L )
{
    // allocate memory
    mBuff   = new char  [ mStorageSize ];
    mTokens = new char* [ mStorageSize ];

    ResetContent();
}

TextLineSplitter::~TextLineSplitter()
{
    delete [] mBuff;
    delete [] mTokens;
}


void TextLineSplitter::SplitLine( const char *line,
                                  const char sep_char   /* = ',' */,
                                )
{
    assert( sep_char != '\0' );

    ResetContent();
    strncpy( mBuff, line, mMaxLineLen );

    size_t idx       = 0L; // running index for characters

    do
    {
        assert( idx < mStorageSize );

        const char chr = line[ idx ]; // retrieve current character

        if( mTokens[ mNumTokens ] == NULL )
        {
            mTokens[ mNumTokens ] = &mBuff[ idx ];
        } // if

        if( chr == sep_char || chr == '\0' )
        { // item or line finished
            // overwrite separator with a 0-terminating character:
            mBuff[ idx ] = '\0';
            // count-up items:
            mNumTokens ++;
        } // if

    } while( line[ idx++ ] );
}

Сценарий использования будет:

// create an instance capable of splitting strings up to 1000 chars long:
TextLineSplitter spl( 1000 );
spl.SplitLine( "Item1,,Item2,Item3" );
for( size_t i = 0; i < spl.NumTokens(); i++ )
{
    printf( "%s\n", spl.GetToken( i ) );
}

выход:

Item1

Item2
Item3
person Angel Sinigersky    schedule 15.05.2011

boost::tokenizer - ваш друг, но подумайте о том, чтобы сделать свой код переносимым со ссылкой на проблемы интернационализации (i18n) с использованием _2 _ / _ 3_ вместо устаревших типов _4 _ / _ 5_.

#include <iostream>
#include <boost/tokenizer.hpp>
#include <string>

using namespace std;
using namespace boost;

typedef tokenizer<char_separator<wchar_t>,
                  wstring::const_iterator, wstring> Tok;

int main()
{
  wstring s;
  while (getline(wcin, s)) {
    char_separator<wchar_t> sep(L" "); // list of separator characters
    Tok tok(s, sep);
    for (Tok::iterator beg = tok.begin(); beg != tok.end(); ++beg) {
      wcout << *beg << L"\t"; // output (or store in vector)
    }
    wcout << L"\n";
  }
  return 0;
}
person jochenleidner    schedule 16.07.2012
comment
legacy определенно неверен, а wchar_t - ужасный тип, зависящий от реализации, который никто не должен использовать без крайней необходимости. - person RamblingMad; 21.05.2014
comment
Использование wchar_t каким-то образом автоматически не решает никаких проблем с i18n. Вы используете кодировки для решения этой проблемы. Если вы разделяете строку с помощью разделителя, подразумевается, что разделитель не конфликтует с закодированным содержимым любого токена внутри строки. Может потребоваться экранирование и т. Д. Wchar_t - не волшебное решение этой проблемы. - person yonil; 07.09.2015

Простой код C ++ (стандарт C ++ 98), принимает несколько разделителей (указанных в std :: string), использует только векторы, строки и итераторы.

#include <iostream>
#include <vector>
#include <string>
#include <stdexcept> 

std::vector<std::string> 
split(const std::string& str, const std::string& delim){
    std::vector<std::string> result;
    if (str.empty())
        throw std::runtime_error("Can not tokenize an empty string!");
    std::string::const_iterator begin, str_it;
    begin = str_it = str.begin(); 
    do {
        while (delim.find(*str_it) == std::string::npos && str_it != str.end())
            str_it++; // find the position of the first delimiter in str
        std::string token = std::string(begin, str_it); // grab the token
        if (!token.empty()) // empty token only when str starts with a delimiter
            result.push_back(token); // push the token into a vector<string>
        while (delim.find(*str_it) != std::string::npos && str_it != str.end())
            str_it++; // ignore the additional consecutive delimiters
        begin = str_it; // process the remaining tokens
        } while (str_it != str.end());
    return result;
}

int main() {
    std::string test_string = ".this is.a.../.simple;;test;;;END";
    std::string delim = "; ./"; // string containing the delimiters
    std::vector<std::string> tokens = split(test_string, delim);           
    for (std::vector<std::string>::const_iterator it = tokens.begin(); 
        it != tokens.end(); it++)
            std::cout << *it << std::endl;
}
person vsoftco    schedule 15.12.2013

/// split a string into multiple sub strings, based on a separator string
/// for example, if separator="::",
///
/// s = "abc" -> "abc"
///
/// s = "abc::def xy::st:" -> "abc", "def xy" and "st:",
///
/// s = "::abc::" -> "abc"
///
/// s = "::" -> NO sub strings found
///
/// s = "" -> NO sub strings found
///
/// then append the sub-strings to the end of the vector v.
/// 
/// the idea comes from the findUrls() function of "Accelerated C++", chapt7,
/// findurls.cpp
///
void split(const string& s, const string& sep, vector<string>& v)
{
    typedef string::const_iterator iter;
    iter b = s.begin(), e = s.end(), i;
    iter sep_b = sep.begin(), sep_e = sep.end();

    // search through s
    while (b != e){
        i = search(b, e, sep_b, sep_e);

        // no more separator found
        if (i == e){
            // it's not an empty string
            if (b != e)
                v.push_back(string(b, e));
            break;
        }
        else if (i == b){
            // the separator is found and right at the beginning
            // in this case, we need to move on and search for the
            // next separator
            b = i + sep.length();
        }
        else{
            // found the separator
            v.push_back(string(b, i));
            b = i;
        }
    }
}

Библиотеки наддува хороши, но они не всегда доступны. Выполнение подобных действий вручную - тоже хорошее упражнение для мозга. Здесь мы просто используем алгоритм std :: search () из STL, см. Приведенный выше код.

person Murphy78    schedule 25.02.2014

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

Вот мой маленький алгоритм, использующий только STL:

//use like this
//std::vector<std::wstring> vec = Split<std::wstring> (L"Hello##world##!", L"##");

template <typename valueType>
static std::vector <valueType> Split (valueType text, const valueType& delimiter)
{
    std::vector <valueType> tokens;
    size_t pos = 0;
    valueType token;

    while ((pos = text.find(delimiter)) != valueType::npos) 
    {
        token = text.substr(0, pos);
        tokens.push_back (token);
        text.erase(0, pos + delimiter.length());
    }
    tokens.push_back (text);

    return tokens;
}

Насколько я тестировал, его можно использовать с разделителями любой длины и формы. Создайте экземпляр с типом строки или wstring.

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

Надеюсь, это поможет.

person robcsi    schedule 17.03.2014

Раньше я делал лексер / токенизатор с использованием только стандартных библиотек. Вот код:

#include <iostream>
#include <string>
#include <vector>
#include <sstream>

using namespace std;

string seps(string& s) {
    if (!s.size()) return "";
    stringstream ss;
    ss << s[0];
    for (int i = 1; i < s.size(); i++) {
        ss << '|' << s[i];
    }
    return ss.str();
}

void Tokenize(string& str, vector<string>& tokens, const string& delimiters = " ")
{
    seps(str);

    // Skip delimiters at beginning.
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);
    // Find first "non-delimiter".
    string::size_type pos = str.find_first_of(delimiters, lastPos);

    while (string::npos != pos || string::npos != lastPos)
    {
        // Found a token, add it to the vector.
        tokens.push_back(str.substr(lastPos, pos - lastPos));
        // Skip delimiters.  Note the "not_of"
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next "non-delimiter"
        pos = str.find_first_of(delimiters, lastPos);
    }
}

int main(int argc, char *argv[])
{
    vector<string> t;
    string s = "Tokens for everyone!";

    Tokenize(s, t, "|");

    for (auto c : t)
        cout << c << endl;

    system("pause");

    return 0;
}
person CATspellsDOG    schedule 15.01.2015

Это простой цикл для токенизации только с файлами стандартной библиотеки.

#include <iostream.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <conio.h>
class word
    {
     public:
     char w[20];
     word()
      {
        for(int j=0;j<=20;j++)
        {w[j]='\0';
      }
   }



};

void main()
  {
    int i=1,n=0,j=0,k=0,m=1;
    char input[100];
    word ww[100];
    gets(input);

    n=strlen(input);


    for(i=0;i<=m;i++)
      {
        if(context[i]!=' ')
         {
            ww[k].w[j]=context[i];
            j++;

         }
         else
        {
         k++;
         j=0;
         m++;
        }

   }
 }
person Karthik    schedule 19.05.2013