Использование strtok с std::string

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

Я старался:

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

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

14 ответов

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

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

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

char *dup = strdup(str.c_str());
token = strtok(dup, " ");
free(dup);
  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));
        }
      }
    }
    

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

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

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

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

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

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

Но это не правильно. На самом деле функция заменяет первое вхождение символа разделителя на \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

С C++17 str::string получает data() перегрузка, которая возвращает указатель на модифицируемый буфер, поэтому строку можно использовать в strtok напрямую без всяких взломов:

#include <string>
#include <iostream>
#include <cstring>
#include <cstdlib>

int main()
{
    ::std::string text{"pop dop rop"};
    char const * const psz_delimiter{" "};
    char * psz_token{::std::strtok(text.data(), psz_delimiter)};
    while(nullptr != psz_token)
    {
        ::std::cout << psz_token << ::std::endl;
        psz_token = std::strtok(nullptr, psz_delimiter);
    }
    return EXIT_SUCCESS;
}

выход

поп
Присадка
ROP

РЕДАКТИРОВАТЬ: использование const cast используется только для демонстрации эффекта 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

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

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

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

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

Ответ Криса , вероятно, подходит при использовании std::string; однако, если вы хотите использовать std::basic_string<char16_t>, std::getline использовать нельзя. Вот возможная другая реализация:

      template <class CharT> bool tokenizestring(const std::basic_string<CharT> &input, CharT separator, typename std::basic_string<CharT>::size_type &pos, std::basic_string<CharT> &token) {
    if (pos >= input.length()) {
        // if input is empty, or ends with a separator, return an empty token when the end has been reached (and return an out-of-bound position so subsequent call won't do it again)
        if ((pos == 0) || ((pos > 0) && (pos == input.length()) && (input[pos-1] == separator))) {
            token.clear();
            pos=input.length()+1;
            return true;
        }
        return false;
    }
    typename std::basic_string<CharT>::size_type separatorPos=input.find(separator, pos);
    if (separatorPos == std::basic_string<CharT>::npos) {
        token=input.substr(pos, input.length()-pos);
        pos=input.length();
    } else {
        token=input.substr(pos, separatorPos-pos);
        pos=separatorPos+1;
    }
    return true;
}

Затем используйте его следующим образом:

      std::basic_string<char16_t> s;
std::basic_string<char16_t> token;
std::basic_string<char16_t>::size_type tokenPos=0;
while (tokenizestring(s, (char16_t)' ', tokenPos, token)) {
    ...
}

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

Это не удается, потому что str.c_str() возвращает постоянную строку, но char * strtok (char * str, const char * delimiters ) требуется изменчивая строка Поэтому вам нужно использовать inorder *const_cast >, чтобы сделать его voletile. Я даю вам полную, но небольшую программу для токенизации строки с помощью функции C 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;
    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;
}

Если вы не возражаете против открытого исходного кода, вы можете использовать классы подбуфера и подпаратера с 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(' ');

Во-первых, я бы сказал, использовать 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]," ");

используя std:wstring.find_first_of() и std::wstring.substr().

std::wstring можно заменить на std:string, а const wchar_t — на const char.

      #include <iostream>
using namespace std;

size_t __wstok(wstring * ws_mystring , wstring * ws_word ,  const wchar_t c)
{//size_t __wstok
   wstring mywstr = * ws_mystring;
   size_t found = mywstr.find_first_of(c) ;

      if (found != wstring::npos)
      {//if (found != wstring::npos)
      *ws_word =  mywstr.substr(0,found) ;
      *ws_mystring = mywstr.substr(found+1 , mywstr.size() );
      }//if (found != wstring::npos)

       if (found == wstring::npos)
       *ws_word = mywstr;

return(found);
}//size_t __wstok

// main
int main()
{
wstring a_wstring = L"every good boy deserves fudge"; 
wstring a_word; // the string where the result is stored every time.

    while (__wstok(&a_wstring, &a_word, L' ' ) != wstring::npos)
    {//while
    wcout <<  a_word.c_str() << L"\n\n";
    }//while
   wcout <<  a_word.c_str() << L"\n\n"; // last string

return(0);
}

(выход)

каждый

хороший

мальчик

заслуживает

выдумка

Приведение к (char*) помогло мне!

token = strtok((char *)str.c_str(), " "); 
Другие вопросы по тегам