Как сделать токенизацию строки в С++?

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

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

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

Ответ 1

Ваш простой случай может быть легко построен с использованием метода std::string::find. Однако взгляните на Boost.Tokenizer. Это здорово. В Boost обычно есть очень крутые струнные инструменты.

Ответ 2

Класс 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;
    }
}

Обновлен для С++ 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;
    }
}

Ответ 3

Здесь реальный простой:

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

Ответ 4

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

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

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

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

Ответ 5

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

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

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

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

Ответ 6

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

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

Ответ 7

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

(Я изначально писал ответ на вопрос Дуга: С++ Strings Modifying and Extracting на основе разделителей (закрыто). Но поскольку Martin York закрыл этот вопрос указателем здесь... Я просто обобщу свой код.)

Ответ 8

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

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

Ответ 9

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

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

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

Ответ 10

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

Qt

#include <QString>

...

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

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

Подробнее о документации Qt

Ответ 11

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

//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());
}

Ответ 12

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

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

Ответ 13

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

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

Ответ 14

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

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

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

Ответ 15

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

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

Ответ 16

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

Ответ 17

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

Ответ 18

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

string word; sin >> word;

Ответ 19

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

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

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

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

Ответ 21

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

Ответ 22

Ответ Адама Пирса предоставляет токенизатор с ручным прикосновением, принимающий const char*. Это немного более проблематично в отношении итераторов, потому что приращение итератора конца string undefined. Тем не менее, учитывая 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);

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

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

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

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

Ответ 23

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

Ответ 24

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

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

Ответ 25

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

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

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

    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 + 1);
        second_pos = tmp.find(" ");
    }

    result.push_back(tmp);

    return result;
}

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

Ответ 27

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

Ответ 28

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

В принципе, можно заменить символы разделителя на "-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

Ответ 29

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

Ответ 30

Простой код С++ (стандартный С++ 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;
}