Почему strtok модифицирует контейнер stl

#c #stl

#c #stl

Вопрос:

В приведенном ниже коде я ожидаю, что результат будет abc #def. Но я получаю вывод как abcdef. Кажется, strtok изменяет вектор, хотя я не передаю вектор напрямую функции strtok. Могу ли я узнать, как это происходит внутри

 std::vector<std::pair<const std::string, int>> x;

std::vector<std::string> z;

int main()
{

    char* pch;
    x.push_back(std::make_pair("abc#def", 1));

    std::string m = x[0].first;

    pch = strtok ((char*)(m.c_str()),"#");

    while (pch != NULL)
    {
        z.push_back(pch);
        pch =strtok (NULL, "#");
    }

    cout<<x[0].first<<endl;

    return 0;
}
  

Комментарии:

1. вы модифицируете std::string buffer, потому что strtok изменяет его параметр (так оно и работает).

2. что я должен сделать, чтобы получить abc #def в выводе с помощью strtok

3. strtok изменяет проанализированную строку. Именно так strtok и работает. Приведение к подобному ((char*)(m.c_str()) — определенно плохая идея.

4. Не используйте strtok . Особенно не в c .

Ответ №1:

Скопированные экземпляры std::string могут использовать один и тот же резервный буфер. То есть x[0] и m могут фактически использовать один и тот же резервный буфер.

Вот почему c_str() член возвращает const char * — вам не разрешено его изменять.

Вы отбрасываете константу, используя приведение в стиле C (char *).

В общем, лучше использовать приведения C : static_cast<>/reinterpret_cast<>/ dynamic_cast<> и const_cast<>, если вам действительно нужно удалить const. Последний предназначен только для взаимодействия со старым C-кодом без квалификаторов const . Вам не нужно использовать его в обычном коде C .

Комментарии:

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

2. Кроме того, совет использовать reinterpret_cast<> здесь вместо этого глуп, потому что это ничего не исправит.

3. @MooingDuck (1): Это единственное объяснение поведения, которое он видит здесь, хотя оно может быть больше не разрешено. (2) Вот почему я сказал «в общем».

Ответ №2:

Вместо использования методов strtok use find_first_of amp; find_first_not_of string

вот так:

 using namespace std;
int main () {
    string s="abc#def";
    string temp = s,res;
    while(temp.size()){
        size_t st,en;
        st = temp.find_first_not_of("#");
        en = temp.find_first_of("#",st);

        res= temp.substr(st,en);
        cout<<res.c_str()<<endl;
        temp=(en == string::npos) ? "" : temp.substr(en);
    }
  return 0;
}
  

Ответ №3:

В вашей реализации c_str() должна быть возвращена ссылка на внутренний char буфер строки без создания какой-либо копии. Со страницы руководства glibc strtok :

ОШИБКИ

Будьте осторожны при использовании этих функций. Если вы их используете, обратите внимание, что:

  • Эти функции изменяют свой первый аргумент.

итак, да, strtok примененный к указателю, возвращаемому из c_str() , изменит строковый буфер.

Вы должны использовать std::getline вместо strtok того, чтобы разделять строку на # :

 std::vector<std::pair<const std::string, int>> x;

int main() {
    x.push_back(std::make_pair("abc#def", 1));

    std::string m = x[0].first;
    std::string token;

    while(std::getline(m, token, '#')) {
        std::cout << token << std::endl;
    }

    cout<< x[0].first <<endl;

    return 0;
}
  

или, если вам действительно нужно использовать strtok , по крайней мере, дублируйте буфер, возвращаемый c_str() with strdup (и помните об free() этом).

Комментарии:

1. На самом деле, нет никакой гарантии (или не было до C 11), которая c_str() вернет указатель на внутреннее представление (хотя я никогда не слышал о реализации, где этого не было). И изменение чего-либо с помощью указателя, возвращаемого, c_str() является неопределенным поведением; в некоторых реализациях это может иметь очень странные результаты.

2. И я бы не стал использовать getline для этого. Что не так с std::find or std::string::find ?

3. @JamesKanze std::getline удобен, если строка содержит более одного # разделителя. Конечно, можно перебирать токены, используя std::find , но я нахожу это довольно громоздким. О c_str , я отредактировал ответ, объясняющий часть «ваша реализация имеет такое поведение».

4. В or нет ничего громоздкого std::find std::string::find , и они легко расширяются до использования одного из набора разделителей, и если вы замените std::find на std::find_if , вы можете определить практически любые критерии в качестве разделителя (например, любой пробел). И, конечно, название также не вводит в заблуждение; использование getline для чего угодно, кроме чтения строк, вводит читателя в заблуждение.