Использование strtok с std :: string - PullRequest
43 голосов
/ 14 ноября 2008

У меня есть строка, которую я хотел бы маркировать. Но функция C strtok() требует, чтобы моя строка была char*. Как я могу сделать это просто?

Я пытался:

token = strtok(str.c_str(), " "); 

, который терпит неудачу, потому что он превращает его в const char*, а не char*

Ответы [ 10 ]

61 голосов
/ 14 ноября 2008
#include <iostream>
#include <string>
#include <sstream>
int main(){
    std::string myText("some-text-to-tokenize");
    std::istringstream iss(myText);
    std::string token;
    while (std::getline(iss, token, '-'))
    {
        std::cout << token << std::endl;
    }
    return 0;
}

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

20 голосов
/ 14 ноября 2008
  1. Если в вашей системе доступно boost (я думаю, что в наши дни это стандартно для большинства дистрибутивов Linux), у него есть класс Tokenizer , который можно использовать.

  2. Если нет, то быстрый Google найдет свернутый вручную токенизатор для std :: string, который вы, вероятно, можете просто скопировать и вставить. Это очень короткий.

  3. И, если вам не нравится ни один из них, то вот функция split (), которую я написал, чтобы облегчить мою жизнь. Он будет разбивать строку на части, используя любые символы в «delim» в качестве разделителей. Части добавляются к вектору "частей":

    void split(const string& str, const string& delim, vector<string>& parts) {
      size_t start, end = 0;
      while (end < str.size()) {
        start = end;
        while (start < str.size() && (delim.find(str[start]) != string::npos)) {
          start++;  // skip initial whitespace
        }
        end = start;
        while (end < str.size() && (delim.find(str[end]) == string::npos)) {
          end++; // skip to end of word
        }
        if (end-start != 0) {  // just ignore zero-length strings.
          parts.push_back(string(str, start, end-start));
        }
      }
    }
    
15 голосов
/ 14 ноября 2008

Дублируйте строку, добавьте токен и освободите ее.

char *dup = strdup(str.c_str());
token = strtok(dup, " ");
free(dup);
6 голосов
/ 20 октября 2009

Есть более элегантное решение.

С помощью std :: string вы можете использовать resize (), чтобы выделить достаточно большой буфер, и & s [0], чтобы получить указатель на внутренний буфер.

В этот момент многие прекрасные люди будут прыгать и кричать на экран. Но это факт. Около 2 лет назад

рабочая группа библиотеки решила (собравшись в Лиллехаммере), что, как и для std :: vector, std :: string также формально, а не просто на практике, должна иметь гарантированный непрерывный буфер.

Другая проблема заключается в том, что strtok () увеличивает размер строки. Документация MSDN гласит:

Каждый вызов strtok изменяет strToken, вставляя нулевой символ после токена, возвращенного этим вызовом.

Но это не правильно. На самом деле функция заменяет вхождение first символа разделителя на \ 0. Без изменений в размере строки. Если у нас есть эта строка:

один два три четыре

мы получим

один \ 0two \ 0 - три \ 0-четыре

Так что мое решение очень простое:


std::string str("some-text-to-split");
char seps[] = "-";
char *token;

token = strtok( &str[0], seps );
while( token != NULL )
{
   /* Do your thing */
   token = strtok( NULL, seps );
}

Прочитать обсуждение на http://www.archivum.info/comp.lang.c++/2008-05/02889/does_std::string_have_something_like_CString::GetBuffer

1 голос
/ 14 ноября 2008

РЕДАКТИРОВАТЬ: использование константного приведения только используется для демонстрации эффекта strtok() при применении к указателю, возвращаемому функцией string :: c_str ().

Вы не должны использовать strtok(), поскольку он изменяет токенизированную строку, что может привести к нежелательному, если не неопределенному поведению, поскольку строка C «принадлежит» экземпляру строки.

#include <string>
#include <iostream>

int main(int ac, char **av)
{
    std::string theString("hello world");
    std::cout << theString << " - " << theString.size() << std::endl;

    //--- this cast *only* to illustrate the effect of strtok() on std::string 
    char *token = strtok(const_cast<char  *>(theString.c_str()), " ");

    std::cout << theString << " - " << theString.size() << std::endl;

    return 0;
}

После вызова strtok() пробел был «удален» из строки или уменьшен до непечатаемого символа, но длина осталась неизменной.

>./a.out
hello world - 11
helloworld - 11

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

1 голос
/ 14 ноября 2008

Предполагая, что под "строкой" вы говорите о std :: string в C ++, вы можете взглянуть на пакет Tokenizer в Boost .

1 голос
/ 14 ноября 2008

Полагаю, язык C или C ++ ...

strtok, IIRC, замените разделители на \ 0. Это то, что он не может использовать константную строку. Чтобы обойти это «быстро», если строка не огромная, вы можете просто strdup () ее. Что целесообразно, если вам нужно сохранить строку неизменной (что предлагает const ...).

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

0 голосов
/ 16 октября 2015

Сбой, потому что str.c_str() возвращает константную строку, но char * strtok (char * str, const char * delimiters ) требует изменяемой строки. Поэтому вам нужно использовать * const_cast > для того, чтобы сделать его волетильным. Я даю вам полную, но небольшую программу для токенизации строки с помощью функции C strtok ().

   #include <iostream>
   #include <string>
   #include <string.h> 
   using namespace std;
   int main() {
       string s="20#6 5, 3";
       // strtok requires volatile string as it modifies the supplied string in order to tokenize it 
       char *str=const_cast< char *>(s.c_str());    
       char *tok;
       tok=strtok(str, "#, " );     
       int arr[4], i=0;    
       while(tok!=NULL){
           arr[i++]=stoi(tok);
           tok=strtok(NULL, "#, " );
       }     
       for(int i=0; i<4; i++) cout<<arr[i]<<endl;


       return 0;
   }

ПРИМЕЧАНИЕ: strtok может не подходить для всех ситуаций, так как строка, передаваемая в функцию, модифицируется путем разбивки на меньшие строки . Пожалуйста, ref , чтобы лучше понять функциональность strtok.

Как работает strtok

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

#include <iostream>
#include <string>
#include <string.h> 
using namespace std;
int main() {
    string s="20#6 5, 3";
    char *str=const_cast< char *>(s.c_str());    
    char *tok;
    cout<<"string: "<<s<<endl;
    tok=strtok(str, "#, " );     
    cout<<"String: "<<s<<"\tToken: "<<tok<<endl;   
    while(tok!=NULL){
        tok=strtok(NULL, "#, " );
        cout<<"String: "<<s<<"\t\tToken: "<<tok<<endl;
    }
    return 0;
}

Выход:

string: 20#6 5, 3

String: 206 5, 3    Token: 20
String: 2065, 3     Token: 6
String: 2065 3      Token: 5
String: 2065 3      Token: 3
String: 2065 3      Token: 

strtok выполняет итерацию по строке. Первый вызов находит символ без делимера (в данном случае 2) и помечает его как маркер start , затем продолжает поиск разделителя и заменяет его на нулевой символ (# заменяется в фактическая строка) и возвращает start , который указывает на начальный символ токена (т. е. он возвращает токен 20, который завершается нулем). При последующем вызове он начинает сканирование со следующего символа и возвращает токен, если найден еще ноль. последовательно возвращает токены 6, 5, 3.

0 голосов
/ 11 июня 2015

Если вы не возражаете против открытого исходного кода, вы можете использовать классы подбуфера и подпарасера ​​из https://github.com/EdgeCast/json_parser. Исходная строка остается без изменений, нет распределения и копирования данных. Я не скомпилировал следующее, поэтому могут быть ошибки.

std::string input_string("hello world");
subbuffer input(input_string);
subparser flds(input, ' ', subparser::SKIP_EMPTY);
while (!flds.empty())
{
    subbuffer fld = flds.next();
    // do something with fld
}

// or if you know it is only two fields
subbuffer fld1 = input.before(' ');
subbuffer fld2 = input.sub(fld1.length() + 1).ltrim(' ');
0 голосов
/ 14 ноября 2008

Во-первых, я бы сказал, использовать Booken Tokenizer.
В качестве альтернативы, если ваши данные разделены пробелами, тогда библиотека строковых потоков очень полезна.

Но оба вышеперечисленных уже были рассмотрены.
Поэтому в качестве третьего варианта C-Like я предлагаю скопировать std :: string в буфер для модификации.

std::string   data("The data I want to tokenize");

// Create a buffer of the correct length:
std::vector<char>  buffer(data.size()+1);

// copy the string into the buffer
strcpy(&buffer[0],data.c_str());

// Tokenize
strtok(&buffer[0]," ");
...