Avatar ·

Как разбить строку на токены (слова) в C++? Подробное руководство

Всем привет! Я только начал изучать C++ и столкнулся с проблемой, которая кажется простой, но я никак не могу найти нормальное решение. Мне нужно разбить строку на отдельные слова (токены). Например, у меня есть строка "Hello, world! This is C++". Я хочу получить массив или вектор: ["Hello,", "world!", "This", "is", "C++"]. В Python я бы просто написал `"Hello, world! This is C++".split()`, и всё готово. Но в C++ я не вижу такого же простого метода. Я знаю, что есть `std::string`, есть `std::stringstream`, но как из них вытащить отдельные слова? Пробовал написать что-то вроде: ```cpp #include #include #include #include int main() { std::string str = "Hello, world! This is C++"; std::stringstream ss(str); std::string word; std::vector tokens; while (ss >> word) { tokens.push_back(word); } for (const auto& w : tokens) { std::cout << w << std::endl; } return 0; } ``` Но это работает только для пробелов. А если мне нужно разбить по запятым, точкам или другим разделителям? Или если разделителей несколько? Например, строка "apple,banana;orange|grape" должна разбиться на ["apple", "banana", "orange", "grape"]. В Python есть `re.split()`, а в C++? Я слышал про `strtok`, но говорят, что это небезопасно и не для C++ строк. Есть ли современный, безопасный и удобный способ сделать это в C++11/14/17? Буду очень благодарен за примеры кода и объяснения! <<>>

Почему простое решение через stringstream не всегда подходит?

Ваш пример с stringstream работает только для разделения по пробелам. Для произвольных разделителей нужно использовать другие подходы. Рассмотрим три современных и безопасных способа.

Способ 1: Разделение по одному символу-разделителю

Самый простой способ для одного разделителя — использовать std::getline с stringstream:

#include <iostream>
#include <sstream>
#include <string>
#include <vector>

std::vector<std::string> splitByChar(const std::string& str, char delimiter) {
    std::vector<std::string> tokens;
    std::stringstream ss(str);
    std::string token;
    while (std::getline(ss, token, delimiter)) {
        if (!token.empty()) {
            tokens.push_back(token);
        }
    }
    return tokens;
}

int main() {
    std::string data = "apple,banana,orange,grape";
    auto tokens = splitByChar(data, ',');
    for (const auto& t : tokens) {
        std::cout << t << " ";
    }
    // Вывод: apple banana orange grape
    return 0;
}

Способ 2: Разделение по нескольким разделителям (современный C++)

Для нескольких разделителей используем std::find_first_of и итераторы:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

std::vector<std::string> splitByMultiple(const std::string& str, const std::string& delimiters) {
    std::vector<std::string> tokens;
    size_t start = 0;
    size_t end = str.find_first_of(delimiters);
    
    while (end != std::string::npos) {
        if (end != start) {
            tokens.push_back(str.substr(start, end - start));
        }
        start = end + 1;
        end = str.find_first_of(delimiters, start);
    }
    
    if (start < str.length()) {
        tokens.push_back(str.substr(start));
    }
    
    return tokens;
}

int main() {
    std::string data = "apple,banana;orange|grape";
    auto tokens = splitByMultiple(data, ",;|");
    for (const auto& t : tokens) {
        std::cout << t << " ";
    }
    // Вывод: apple banana orange grape
    return 0;
}

Способ 3: Использование регулярных выражений (C++11)

Самый гибкий, но немного медленный способ:

#include <iostream>
#include <string>
#include <vector>
#include <regex>

std::vector<std::string> splitByRegex(const std::string& str, const std::string& pattern) {
    std::vector<std::string> tokens;
    std::regex re(pattern);
    std::sregex_token_iterator it(str.begin(), str.end(), re, -1);
    std::sregex_token_iterator end;
    
    while (it != end) {
        tokens.push_back(*it);
        ++it;
    }
    return tokens;
}

int main() {
    std::string data = "Hello, world! This is C++";
    // Разделяем по пробелам и знакам препинания
    auto tokens = splitByRegex(data, R"([\s,;:.!?]+)");
    for (const auto& t : tokens) {
        std::cout << t << " ";
    }
    // Вывод: Hello world This is C++
    return 0;
}

Что выбрать?

  • Один разделитель — используйте std::getline (просто и быстро).
  • Несколько символов-разделителей — find_first_of (хорошая производительность).
  • Сложные паттерны разделения — регулярные выражения (максимальная гибкость, но медленнее).

Избегайте strtok из C — он модифицирует исходную строку и небезопасен. Все приведённые решения работают с std::string и совместимы с C++11 и новее.

<<>>

Альтернативный подход: использование boost::algorithm::split

Если вы используете библиотеку Boost (а это очень популярная библиотека для C++), то задача упрощается до одной строки:

#include <boost/algorithm/string.hpp>
#include <string>
#include <vector>

int main() {
    std::string str = "apple,banana;orange|grape";
    std::vector<std::string> tokens;
    boost::split(tokens, str, boost::is_any_of(",;|"));
    
    for (const auto& t : tokens) {
        std::cout << t << " ";
    }
    // Вывод: apple banana orange grape
    return 0;
}

boost::split автоматически обрабатывает пустые токены (их можно исключить с помощью boost::token_compress_on). Это самый короткий и надёжный вариант, если Boost уже подключён к проекту.

Также можно использовать boost::tokenizer для более сложных случаев:

#include <boost/tokenizer.hpp>

std::string str = "Hello, world! This is C++";
boost::tokenizer<boost::char_separator<char>> tokens(str, boost::char_separator<char>(" ,;:.!?"));
for (const auto& t : tokens) {
    std::cout << t << " ";
}

Оба подхода из Boost — это production-ready решения, которые используются в тысячах проектов. Если вы не хотите подключать Boost, используйте решения из первого ответа — они тоже отлично работают.