Итерация по словам строки в C++: 5 способов от новичка до профи

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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

Представьте: вы пишете анализатор логов, парсер команд или просто хотите подсчитать слова в предложении. Первое, что приходит в голову — взять строку и пройтись по каждому слову отдельно. В Python это делается в одну строчку: "hello world".split(). Но мы в C++, а значит, простота — это роскошь, которую нужно заслужить.

Вопрос "How do I iterate over the words of a string?" на Stack Overflow собрал сотни тысяч просмотров. И это не случайно: новички тонут в нюансах работы с памятью, а профи ищут баланс между скоростью и читаемостью. В этой статье мы разберём 5 способов итерации по словам в C++, сравним их производительность и поймём, когда какой использовать.

Если вы когда-нибудь писали strtok и получали segfault, или пытались вставить std::regex в горячий цикл — эта статья для вас.

Способ №1: Классика — std::istringstream

Самый безопасный и интуитивный способ для новичков. std::istringstream работает как поток: вы просто читаете из него слова, разделённые пробелами.

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

int main() {
    std::string text = "C++ is not for the faint of heart";
    std::istringstream stream(text);
    std::string word;
    std::vector<std::string> words;
    
    while (stream >> word) {
        words.push_back(word);
        std::cout << "Word: " << word << std::endl;
    }
    
    std::cout << "Total words: " << words.size() << std::endl;
    return 0;
}

Плюсы: чистый код, никаких сырых указателей, автоматическое управление памятью. Работает с любыми разделителями-пробелами (табуляция, перевод строки).

Минусы: медленнее ручных методов из-за создания потока. Не подходит для кастомных разделителей (только пробельные символы).

Когда использовать: когда читаемость важнее скорости. Идеально для учебных примеров и прототипов.

Способ №2: Быстрый и опасный — std::strtok

Наследие из C, которое до сих пор живёт в продакшене. strtok модифицирует исходную строку (вставляет нулевые символы) и использует статический буфер.

#include <iostream>
#include <cstring>
#include <vector>

int main() {
    char text[] = "Fast but dangerous — handle with care";
    std::vector<char*> words;
    
    char* token = std::strtok(text, " ");
    while (token != nullptr) {
        words.push_back(token);
        std::cout << "Word: " << token << std::endl;
        token = std::strtok(nullptr, " ");
    }
    
    std::cout << "Total words: " << words.size() << std::endl;
    return 0;
}

Плюсы: очень быстрый (работает на уровне C-строк). Позволяет задать любой набор разделителей.

Минусы: не thread-safe, разрушает исходную строку, работает только с char*, а не std::string. Легко получить undefined behavior.

Когда использовать: только в горячих циклах, где каждая наносекунда на счету, и вы уверены в однопоточности. В современном C++ лучше избегать.

Способ №3: Ручной перебор — std::string::find и substr

Иногда нужно полное control. Ручной перебор даёт гибкость и не требует дополнительных библиотек.

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

std::vector<std::string> splitManual(const std::string& text, char delimiter = ' ') {
    std::vector<std::string> tokens;
    size_t start = 0;
    size_t end = text.find(delimiter);
    
    while (end != std::string::npos) {
        tokens.push_back(text.substr(start, end - start));
        start = end + 1;
        end = text.find(delimiter, start);
    }
    tokens.push_back(text.substr(start));
    
    return tokens;
}

int main() {
    std::string text = "Manual control gives you power";
    auto words = splitManual(text);
    
    for (const auto& word : words) {
        std::cout << "Word: " << word << std::endl;
    }
    return 0;
}

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

Минусы: много кода, легко ошибиться с граничными случаями (пустая строка, несколько разделителей подряд).

Когда использовать: когда нужен кастомный парсинг, например, разбор CSV или сложных форматов.

Способ №4: Современный и мощный — std::regex и std::sregex_token_iterator

Регулярные выражения — это швейцарский нож для работы со строками. В C++11 появилась полноценная поддержка regex, и теперь можно разбивать строки буквально одной командой.

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

int main() {
    std::string text = "Regex: powerful but slow";
    std::regex word_regex(R"(\w+)"); // Только буквенно-цифровые символы
    std::vector<std::string> words;
    
    auto words_begin = std::sregex_iterator(text.begin(), text.end(), word_regex);
    auto words_end = std::sregex_iterator();
    
    for (auto it = words_begin; it != words_end; ++it) {
        words.push_back(it->str());
        std::cout << "Word: " << it->str() << std::endl;
    }
    
    std::cout << "Total words: " << words.size() << std::endl;
    return 0;
}

Плюсы: невероятная гибкость — можно игнорировать знаки препинания, выделять только цифры, обрабатывать Unicode. Читаемый код для сложных паттернов.

Минусы: медленный (компиляция regex — дорогая операция). Не подходит для горячих путей.

Когда использовать: когда сложность паттерна оправдывает затраты. Отличный выбор для конфигурационных файлов, логов, текстовой аналитики.

Способ №5: Boost — boost::split и boost::tokenizer

Если вы уже используете Boost (а это половина C++ сообщества), то

Блоги

Рекомендаций по книгам