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

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

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

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

Не могу поверить, что эта рутинная задача - такая головная боль в C++

wfbarksdale 08.09.2011 09:05

В C++ это не головная боль - есть разные способы добиться этого. программисты менее осведомлены о C++, чем о C# - речь идет о маркетинге и инвестициях ... см. здесь различные варианты C++ для достижения того же: cplusplus.com/faq/sequences/strings/split

hB0 31.10.2013 04:10

@ hB0 отвечает на множество вопросов и все еще не решает, что делать - это головная боль. одному нужна эта библиотека, другому - просто пробелы, а третьему пробелы не нужны ..

Paschalis 14.04.2016 20:02

Возможный дубликат Разделить строку в C++?

KOB 08.05.2017 22:16

Почему в C++ все должно быть нелегко?

Wael Assaf 13.08.2019 13:26

C++ является высокопроизводительным языком, поэтому он не способствует универсальному подходу к манипуляциям со строками. Для этого приложения иногда все, что вам нужно, - это последний токен или несколько первых токенов, но «разделение» будет токенизировать все без каких-либо остановок на основе необходимости. Тем не менее, строковые операции на любом языке обходятся дорого, поэтому стандарт C++ явно высказался против чрезмерного упрощения интерфейсов, скрывающих настоящую сложность.

John Phu Nguyen 08.05.2020 01:02
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
434
6
576 502
35
Перейти к ответу Данный вопрос помечен как решенный

Ответы 35

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

//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());
}
Ответ принят как подходящий

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

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

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

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

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{}
);

К сожалению, ускорение доступно не для всех проектов. Придется искать ответ без повышения.

FuzzyBunnySlippers 21.12.2013 01:00

Не каждый проект открыт с «открытым исходным кодом». Я работаю в строго регулируемых отраслях. На самом деле это не проблема. Это просто факт жизни. Буст доступен не везде.

FuzzyBunnySlippers 21.12.2013 03:19

@NonlinearIdeas Другой вопрос / ответ был совсем не о проектах с открытым исходным кодом. То же самое и с проектом любой. Тем не менее, я, конечно, понимаю об ограниченных стандартах, таких как MISRA C, но тогда я понял, что вы все равно создаете все с нуля (если только вам не удастся найти совместимую библиотеку - редкость). Во всяком случае, дело вряд ли в том, что «Boost недоступен» - дело в том, что у вас есть особые требования, для которых почти универсальный ответ любой не подходит.

Konrad Rudolph 21.12.2013 14:46

@NonlinearIdeas В данном случае другие ответы, не связанные с Boost, также не соответствуют требованиям MISRA.

Konrad Rudolph 21.12.2013 14:47

Это обсуждение побудило меня спросить о моей конкретной отрасли, вызывающей озабоченность: stackoverflow.com/questions/20714009/….

FuzzyBunnySlippers 21.12.2013 16:02

@FuzzyBunnySlippers Соревнования по программированию вообще не позволяют использовать библиотеки.

noɥʇʎԀʎzɐɹƆ 07.07.2016 18:24

Я пытался установить boost 3 раза, каждый раз меня обескураживал STL barf. Это 2016 год, не можем ли мы просто заменить препроцессор C на PHP или JavaScript и встроить его в наши файлы C++, чтобы мы использовали реальные функции преобразования, которые преобразуют строку в строку и выдают нормальную ошибку, если что-то пойдет не так, вместо архаичного #ifdef #ifndef, которые даже не имеют смысла в C / C++, поскольку язык хочет быть сжатым до «одной строки», но для макросов требуются собственные строки. Даже Perl в качестве препроцессора был бы лучше, чем использование # ifdefs / # ifndef / template, и это не преобразования в черный ящик.

Dmitry 11.09.2016 20:23

@Dmitry Что за STL barf ?! И все сообщество очень поддерживает замену препроцессора C - на самом деле, есть предложения сделать это. Но ваше предложение использовать вместо него PHP или какой-либо другой язык было бы огромным шагом назад.

Konrad Rudolph 11.09.2016 20:43

@KonradRudolph от STL barf I ~ 2-3 экрана расширенных шаблонов, которые часто показывают ошибку в коде, которого нет в вашей программе, что трудно понять, что пошло не так для людей, которые к этому не привыкли. Тем не менее, с тех пор я многому научился, поэтому, думаю, я снова попробую настроить ускорение, это то, что я смогу настроить.

Dmitry 11.09.2016 21:06

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

Обратите внимание, что strtok изменяет строку, которую вы проверяете, поэтому вы не можете использовать ее в строках const char * без создания копии.

Graeme Perrow 10.09.2008 17:53

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

JohnMcG 10.09.2008 19:09

@JohnMcG Или просто используйте strtok_s, который по сути является strtok с явной передачей состояния.

Matthias 02.06.2018 19:51

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

#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;
}

мне нужно добавить прототип этого метода в файл .h?

Suhrob Samiev 22.12.2011 12:26

Это не совсем лучший ответ, поскольку он по-прежнему использует строковый литерал, который представляет собой простой массив константных символов C. Я полагаю, что спрашивающий спрашивал, может ли он токенизировать строку C++, которая имеет тип «строка», введенный последним.

Vijay Kumar Kanta 19.04.2017 10:05

Здесь нужен новый ответ, потому что я сильно подозреваю, что включение регулярных выражений в C++ 11 изменило наилучший ответ.

Omnifarious 25.10.2017 18:45

На этот ответ есть проблема со строками, в которых первый / последний символ равен разделителю. например строка "a" результаты будет ["", "a"].

y30 13.11.2020 14:26

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

string word; sin >> word;

Моя вина, что я привел плохой (слишком простой) пример. Насколько я знаю, это работает только тогда, когда ваш разделитель - пробел.

Bill the Lizard 25.11.2008 21:24

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

#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);
}

Я нахожу эти std :: раздражающим при чтении .. почему бы не использовать "using"?

user35978 28.11.2008 07:19

@Vadi: потому что редактирование чужого поста довольно навязчиво. @pheze: Я предпочитаю, чтобы std знал, откуда взялся мой объект, это просто вопрос стиля.

Matthieu M. 02.04.2010 12:49

Я понимаю вашу причину и думаю, что это действительно хороший выбор, если он работает для вас, но с педагогической точки зрения я действительно согласен с pheze. Легче читать и понимать совершенно чужой пример, такой как этот, с «using namespace std» вверху, потому что он требует меньше усилий для интерпретации следующих строк ... особенно в этом случае, потому что все взято из стандартной библиотеки. Вы можете сделать его легко читаемым и очевидным, откуда берутся объекты, с помощью серии «using std :: string;» и т.д. Тем более, что функция такая короткая.

cheshirekow 16.07.2010 15:27

Честно говоря, функционально это было бы намного удобнее. А эти std :: просто уродливы. Я отредактировал, но не ожидаю, что он появится где-нибудь в ближайшем будущем. Тем не менее попробую.

Arnthor 16.10.2011 18:33

user - потому что кто-то может скопировать этот пример, и мы бы не хотели, чтобы они использовали 'using' :)

Good Person 31.01.2012 04:42

Несмотря на то, что префиксы "std ::" раздражают или уродливы, лучше всего включить их в пример кода, чтобы было совершенно ясно, откуда берутся эти функции. Если они вас беспокоят, просто заменить их словом «использующий» после того, как вы украдете пример и заявите его как свой собственный.

dlchambers 11.04.2012 18:54

Ага! что он сказал! рекомендуется использовать префикс std. Любая большая база кода, без сомнения, будет иметь свои собственные библиотеки и пространства имен, а использование «using namespace std» вызовет у вас головную боль, когда вы начнете вызывать конфликты пространств имен.

Miek 18.07.2012 21:08

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

Skizz 15.04.2015 14:08

Не могли бы вы обновить свой ответ для std::wstring? Кажется, я слишком тупой, чтобы заставить его компилировать :(

YePhIcK 06.11.2015 07:51

Вам не нужны #include <istream> или <ostream>.

Martin Broadhurst 09.02.2016 01:40

@Miek Особенно при использовании Boost и стандартной библиотеки (<regex>), которая может быть boost::regex или std::regex

Nubcake 13.08.2017 02:03

Используйте 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. Это уместно, это просто, и вам не нужно писать много лишнего кода :-)

Не то чтобы мне не нравился C, однако strtok не является потокобезопасным, и вы должны быть уверены, что отправляемая строка содержит нулевой символ, чтобы избежать возможного переполнения буфера.

tloach 10.05.2010 17:18

Есть strtok_r, но это вопрос C++.

Prof. Falken 06.10.2010 13:14

@tloach: в компиляторе MS C++ strtok является потокобезопасным, поскольку внутренняя статическая переменная создается в TLS (локальное хранилище потока) (на самом деле это зависит от компилятора)

Ahmed Said 28.11.2010 18:03

вы можете проверить это nibuthomas.wordpress.com/2008/06/25/…

Ahmed Said 28.11.2010 18:03

@ahmed: потокобезопасность означает больше, чем просто возможность дважды запустить функцию в разных потоках. В этом случае, если поток изменяется во время выполнения strtok, можно сделать так, чтобы строка была действительной в течение всего запуска strtok, но strtok все равно будет ошибаться, потому что строка изменилась, теперь она уже прошла нулевой символ и собирается продолжайте читать память, пока она не обнаружит нарушение безопасности или не найдет нулевой символ. Это проблема исходных строковых функций C, если вы не укажете длину где-нибудь, у вас возникнут проблемы.

tloach 29.11.2010 16:23

strtok требует указателя на неконстантный массив символов с завершающим нулем, что не является обычным явлением, которое можно найти в коде C++ ... какой ваш любимый способ преобразовать в это из std :: string?

fuzzyTew 03.08.2013 18:43

@tloach: при каких обстоятельствах строка нет может содержать нулевой символ? Я только знаю, что иногда к строкам добавляется '\ 0' ... (вопрос нуба, я знаю!)

The Chaz 2.0 18.09.2013 22:28

Это хорошее решение, хорошо подходящее для проектов C. Однако он несовместим с литералами или объектами строкового типа C++. Похоже, C++ требует класса для всего. > _ <

Vijay Kumar Kanta 04.11.2016 11:48
strtok_r is thread-safe and better to use when available. Just try it to see if your compiler supports it. It probably does.
Gabriel Staples 25.09.2019 23:26

@fuzzyTew Я не сторонник использования strtok в коде C++. Однако, когда мне нужно было преобразовать устаревший код, используя его, чтобы избежать голых указателей (и отсутствие у них гарантий, не относящихся к RAII, что то, на что они указывают, будет освобождено, если возникнет исключение), я использовал «vector<char> foo(str.begin(), str.end()+1); char *p = strtok(foo.data(), " ");». vector владеет доступной для записи копией строковых данных и освободит ее в случае возникновения исключения. (+1 означает, что нулевой признак конца строки скопирован в вектор.)

Some Guy 03.07.2020 02:32

Класс Повышение токенизатора может сделать такие вещи довольно простыми:

#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;
    }
}

Хорошая штука, недавно я этим воспользовался. У моего компилятора Visual Studio есть странное нытье, пока я не использую пробел для разделения двух символов ">" перед битом токенов (текст, sep): (ошибка C2947: ожидание '>' для завершения списка аргументов-шаблонов, найдено '> > ')

AndyUK 01.10.2010 19:57

@AndyUK да, без пробела компилятор анализирует его как оператор извлечения, а не как два закрывающих шаблона.

EnabrenTane 14.06.2011 07:23

Теоретически это исправлено в C++ 0x

David Souther 01.09.2011 06:09

остерегайтесь третьих параметров конструктора char_separator (по умолчанию - drop_empty_tokens, альтернативой является keep_empty_tokens).

Benoit 17.02.2012 14:56

@DavidSouther Говоря о C++ 0x - BOOST_FOREACH теперь можно заменить новым синтаксисом цикла for. for (строка t: токены) {...}

Tomas Andrle 29.02.2012 19:03

Двойное >> не должно быть проблемой для C++ 11, в этом случае компиляторы VS2010 + имеют ошибку, и вы должны зарегистрировать ее при подключении.

paulm 22.07.2013 00:51

@puk - это часто используемый суффикс для файлов заголовков C++. (как .h для заголовков C)

Ferruccio 13.12.2013 02:44

Способствовать росту имеет сильную функцию разделения: усиление :: алгоритм :: сплит.

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

#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"
""

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

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 (карты ввода-вывода, камеры). Я никогда не использую эту функцию для чего-то более сложного или критичного по времени, чем чтение внешних файлов конфигурации при запуске.

Не обижайтесь, ребята, но для такой простой задачи вы слишком усложняете путь. Есть много причин использовать Способствовать росту. Но для чего-то такого простого, это все равно, что сбить муху сани 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++ на основе разделителей (закрыто). Но поскольку Мартин Йорк закрыл этот вопрос указателем здесь ... я просто обобщу свой код.)

Зачем определять макрос, который вы используете только в одном месте. И чем ваш UASSERT лучше стандартного assert. Такое разделение сравнения на 3 токена не делает ничего, кроме как требует больше запятых, чем вам могло бы потребоваться.

crelbor 13.05.2011 17:10

Может быть, макрос UASSERT показывает (в сообщении об ошибке) фактическую взаимосвязь между (и значениями) двух сравниваемых значений? ИМХО, это действительно неплохая идея.

GhassanPL 17.03.2012 21:03

Ух, а почему в классе std::string нет функции split ()?

Mr. Shickadance 19.04.2012 00:34

Я думаю, что последняя строка в цикле while должна быть start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());, а цикл while - while (start != string::npos). Кроме того, я проверяю подстроку, чтобы убедиться, что она не пуста, прежде чем вставлять ее в вектор.

John K 01.08.2012 00:11

@JohnK Если входные данные имеют два последовательных разделителя, очевидно, что строка между ними пуста и должна быть вставлена ​​в вектор. Если пустые значения неприемлемы для конкретной цели, это другое дело, но ИМХО такие ограничения должны применяться за пределами этого вида функций очень общего назначения.

Lauri Nurmi 26.06.2013 18:02

Почему бы не разрешить также пустую строку в качестве разделителя?

user5818995 13.08.2017 23:11

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

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

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

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

У меня были проблемы с использованием этого метода с символами 0x0A в строке, из-за чего цикл while завершился преждевременно. В противном случае это хорошее простое и быстрое решение.

Ryan H. 25.01.2011 02:00

Это хорошо, но помните, что при этом разделитель по умолчанию '\ n' не учитывается. Этот пример будет работать, но если вы используете что-то вроде: while (getline (inFile, word, '')), где inFile - это объект ifstream, содержащий несколько строк, вы получите забавные результаты.

hackrock 20.06.2012 01:28

жаль, что getline возвращает поток, а не строку, что делает его непригодным для использования в списках инициализации без временного хранилища

fuzzyTew 03.08.2013 16:34

Круто! Никакого ускорения и C++ 11, хорошее решение для старых проектов!

Deqing 30.04.2014 11:09

ЭТО и ответ, название функции немного неудобно.

Nils 02.06.2019 00:31

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

Эта функция Tokenize () будет пропускать пустые токены, например, если в основной строке есть подстрока «%%», пустой токен не возвращается. Это пропущено.

Sheen 20.01.2011 19:49

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

Qt

#include <QString>

...

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

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

Смотрите больше на Документация Qt

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

#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;
}

Я бы сделал while ( is >> tmps ) { std::cout << tmps << "\n"; }

jordix 05.04.2016 17:46

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

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

+1 за предложение регулярного выражения, если вам не нужна скорость деформации, это наиболее гибкое решение, которое еще не везде поддерживается, но со временем это станет менее важным.

odinthenerd 27.07.2014 23:57

+1 от меня, только что попробовал <regex> в C++ 11. Так просто и элегантно

StahlRat 06.11.2014 19:30

Если известна максимальная длина токенизируемой входной строки, можно воспользоваться этим и реализовать очень быструю версию. Ниже я набросал основную идею, которая была вдохновлена ​​как 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

вы можете воспользоваться 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;
}

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, "-");

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

Ross 15.06.2012 02:55

вау, серьезно ты только что сделал мне день !! не знал о pystring. это сэкономит мне много времени!

accraze 10.02.2015 20:14

boost::tokenizer - ваш друг, но подумайте о том, чтобы сделать свой код переносимым со ссылкой на проблемы интернационализации (i18n), используя wstring / wchar_t вместо устаревших типов string / char.

#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;
}

"legacy" определенно неверно, а wchar_t - ужасный тип, зависящий от реализации, который никто не должен использовать без крайней необходимости.

RamblingMad 21.05.2014 04:09

Использование wchar_t каким-то образом автоматически не решает никаких проблем с i18n. Вы используете кодировки для решения этой проблемы. Если вы разделяете строку с помощью разделителя, подразумевается, что разделитель не конфликтует с закодированным содержимым любого токена внутри строки. Может потребоваться экранирование и т. д. Wchar_t не является волшебным решением этой проблемы.

yonil 07.09.2015 20:50

Здесь много слишком сложных предложений. Попробуйте это простое решение 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;
}

Вот подход, который позволяет вам контролировать, включены ли пустые токены (например, 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;
}

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

#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++;
        }

   }
 }

Простой код 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;
}

Я отправил этот ответ на аналогичный вопрос. Не изобретайте велосипед. Я использовал несколько библиотек, и самая быстрая и гибкая, с которой мне приходилось сталкиваться, это: Библиотека 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;
}

/// 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, см. Приведенный выше код.

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

Вот мой маленький алгоритм, использующий только 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.

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

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

Мне кажется странным, что, несмотря на то, что все мы, ботаники, осознающие скорость, здесь, на 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

Можно ли токенизировать с помощью разделителя строк?

Galigator 26.07.2014 17:51

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

odinthenerd 27.07.2014 17:50

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

#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;
    }
}

Это должен быть самый высокий рейтинг. Это правильный способ сделать это в C++> = 11.

Omnifarious 25.10.2017 19:12

Я рад, что пролистал до этого ответа (на данный момент было только 9 голосов). Именно так должен выглядеть код C++ 11 для этой задачи!

YePhIcK 07.01.2018 20:17

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

Andrew 06.02.2018 23:39

Отличный ответ, обеспечивающий максимальную гибкость в разделителях. Несколько предостережений: использование \ s + regex позволяет избежать пустых токенов в середине текста, но дает пустой первый токен, если текст начинается с пробела. Кроме того, регулярное выражение кажется медленным: на моем ноутбуке для 20 МБ случайного текста требуется 0,6 секунды, по сравнению с 0,014 секунды для strtok, strsep или ответа Пархема с использованием str.find_first_of, или 0,027 секунды для Perl, или 0,021 секунды для Python. . Для короткого текста скорость может не иметь значения.

Mark Gates 21.04.2018 19:29

Это круто. Спасибо, что поделился.

Vijay Rajanna 19.09.2018 03:57

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

Marek R 18.07.2019 15:06

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

#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;
}

Это простое решение только для 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);
    }
}

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

Это хороший вариант, но я думаю, вам нужно использовать find_first_of () вместо find (), чтобы он работал правильно с несколькими разделителями.

user755921 27.11.2015 22:28

@ user755921 несколько разделителей пропускаются при нахождении начальной позиции с помощью find_first_not_of.

Beginner 05.10.2018 15:38

Ответ Адама Пирса обеспечивает ручной токенизатор, принимающий 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;
}

Live Example


Если вы ищете абстрактную сложность, используя стандартные функции, О Фройнд предлагаетstrtok - простой вариант:

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 должен быть скопирован , то с копией можно будет работать

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


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

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

Live Example

Требуемая конструкция 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() };

Live Example

Кстати, strtok_s - это стандарт C11. strtok_r - это стандарт POSIX2001. Между ними есть стандартная реентерабельная версия strtok для большинства платформ.

Andon M. Coleman 14.12.2016 12:43

@ AndonM.Coleman Но это вопрос C++, а в C++ #include <cstring> включает только версию strtokc99. Итак, я предполагаю, что вы просто предоставляете этот комментарий в качестве вспомогательного материала, демонстрируя доступность расширений strtok для конкретной реализации?

Jonathan Mee 14.12.2016 15:10

Просто это не так уж и нестандартно, как люди могли бы подумать. strtok_s предоставляется как C11, так и как отдельное расширение в среде выполнения Microsoft C. Здесь есть любопытная история, когда функции Microsoft _s стали стандартом C.

Andon M. Coleman 14.12.2016 16:48

@ AndonM.Coleman Верно, я с тобой. Очевидно, что если это в стандарте C11, на интерфейс и реализацию накладываются ограничения, которые требуют идентичного поведения независимо от платформы. Теперь единственная проблема - обеспечить доступность функции C11 для всех платформ. Надеюсь, что стандарт C11 будет чем-то, что C++ 17 или C++ 20 выберет для использования.

Jonathan Mee 14.12.2016 17:13

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

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;
}

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

Обновлено: добавлен общий разделитель

Использовал ваше решение из толпы :) Могу ли я изменить ваш код, чтобы добавить какой-либо разделитель?

Zac 21.11.2019 20:32

@Zac рад, что вам понравилось, и, конечно, вы можете его изменить ... просто добавьте раздел обновлений жирным шрифтом к моему ответу ...

NutCracker 21.11.2019 23:52

Вот мой 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;
}

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

kayleeFrye_onDeck 09.08.2019 23:55

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

mattshu 12.01.2020 17:05

Спасибо @mattshu! Сегменты регулярных выражений делают его сложным или что-то еще?

kayleeFrye_onDeck 16.01.2020 23:50

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

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

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

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

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

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

Другие вопросы по теме