Avatar ·

Как перебрать слова в строке на C++: разбор популярных методов

Всем привет! Я новичок в C++ и никак не могу понять, как правильно разбить строку на отдельные слова и пройтись по ним в цикле. В Python это делается одной строкой через `split()`, а в C++ всё как-то сложно. У меня есть строка вида: `"Привет, как дела? У меня всё хорошо!"` Мне нужно вывести каждое слово отдельно, игнорируя знаки препинания и пробелы. Я пробовал использовать `std::stringstream`, но он не убирает запятые и вопросительные знаки — они прилипают к словам. Например, получается `"Привет,"` вместо `"Привет"`. Пытался написать свой велосипед с ручным поиском пробелов через `std::string::find`, но код получается огромным и запутанным. К тому же мой вариант не работает с табуляциями и множественными пробелами. Подскажите, пожалуйста, как это сделать правильно и красиво? Желательно на современном C++ (11/14/17). Приведите пример кода, который будет: 1. Разбивать строку на слова по пробелам и знакам препинания. 2. Игнорировать лишние пробелы и пустые строки. 3. Работать с любыми разделителями (пробел, табуляция, запятая, точка, восклицательный знак и т.д.). Заранее спасибо! <<>>

Решение с использованием std::stringstream и std::string::find_first_of

В C++ нет встроенного метода `split()`, как в Python, но задачу можно решить элегантно с помощью стандартных средств. Рассмотрим два подхода: базовый (с ручной очисткой пунктуации) и продвинутый (с использованием алгоритмов STL).

1. Базовый подход: std::stringstream + фильтрация

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

#include <iostream>
#include <sstream>
#include <string>
#include <cctype>  // для ispunct

// Функция удаления знаков препинания из строки
std::string cleanWord(const std::string& word) {
    std::string result;
    for (char ch : word) {
        if (!std::ispunct(static_cast<unsigned char>(ch))) {
            result += ch;
        }
    }
    return result;
}

int main() {
    std::string text = "Привет, как дела? У меня всё хорошо!";
    std::istringstream iss(text);
    std::string word;

    while (iss >> word) {
        std::string cleaned = cleanWord(word);
        if (!cleaned.empty()) {
            std::cout << cleaned << std::endl;
        }
    }
    return 0;
}

Результат:

Привет
как
дела
У
меня
всё
хорошо

Оператор >> для std::istringstream автоматически пропускает пробелы и табуляции, но не убирает пунктуацию. Поэтому мы чистим каждое слово вручную через std::ispunct.

2. Продвинутый подход: разбиение по любым разделителям

Этот метод использует std::string::find_first_of и std::string::find_first_not_of для поиска границ слов по набору разделителей. Он работает быстрее и гибче.

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

std::vector<std::string> split(const std::string& str, const std::string& delimiters) {
    std::vector<std::string> tokens;
    size_t start = 0;
    size_t end = 0;

    while ((start = str.find_first_not_of(delimiters, end)) != std::string::npos) {
        end = str.find_first_of(delimiters, start);
        tokens.push_back(str.substr(start, end - start));
    }
    return tokens;
}

int main() {
    std::string text = "Привет, как дела? У меня всё хорошо!";
    std::string delimiters = " ,.!?;:\t\n";  // разделители: пробел, знаки препинания, табуляция
    
    auto words = split(text, delimiters);
    for (const auto& word : words) {
        std::cout << word << std::endl;
    }
    return 0;
}

3. Современный C++17: std::string_view

Для оптимизации можно использовать std::string_view, который не копирует данные, а создаёт представление над исходной строкой:

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

std::vector<std::string_view> splitSV(std::string_view str, std::string_view delimiters) {
    std::vector<std::string_view> tokens;
    size_t start = 0;
    size_t end = 0;

    while ((start = str.find_first_not_of(delimiters, end)) != std::string_view::npos) {
        end = str.find_first_of(delimiters, start);
        tokens.push_back(str.substr(start, end - start));
    }
    return tokens;
}

int main() {
    std::string text = "Привет, как дела? У меня всё хорошо!";
    auto words = splitSV(text, " ,.!?;:\t\n");
    for (auto word : words) {
        std::cout << word << std::endl;
    }
    return 0;
}

Что выбрать?

  • Для обучения и простоты — первый вариант с std::stringstream.
  • Для производительности и гибкости — второй вариант с find_first_of.
  • Для современного C++ — третий вариант с std::string_view (C++17+).

Все три подхода корректно обрабатывают множественные пробелы, табуляции и пустые строки. Выбирайте под свою задачу!

<<>>

Альтернатива: использование boost::split или std::regex

Если вы не ограничены стандартной библиотекой и можете использовать Boost, то задача упрощается до одной строки:

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

int main() {
    std::string text = "Привет, как дела? У меня всё хорошо!";
    std::vector<std::string> words;
    boost::split(words, text, boost::is_any_of(" ,.!?;:\t\n"), boost::token_compress_on);
    
    for (const auto& word : words) {
        if (!word.empty()) {
            std::cout << word << std::endl;
        }
    }
    return 0;
}

boost::token_compress_on автоматически объединяет последовательные разделители, что избавляет от пустых слов.

Решение через std::regex

Ещё один способ — использовать регулярные выражения из C++11:

#include <iostream>
#include <string>
#include <regex>
#include <iterator>

int main() {
    std::string text = "Привет, как дела? У меня всё хорошо!";
    std::regex word_regex(R"(\w+)");  // ищем последовательности букв и цифр
    
    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) {
        std::cout << it->str() << std::endl;
    }
    return 0;
}

Минусы: std::regex может быть медленнее для больших строк, и он не всегда корректно обрабатывает Unicode (например, русские буквы). Для английского текста — отличный вариант.

Итог: Если проект уже использует Boost — берите boost::split. Если хотите универсальности — используйте чистый C++ с find_first_of. Для простых задач — std::stringstream.