Как итерироваться по словам в строке на C++: разбор строки на токены
Решение 1: Использование std::istringstream для базового разбиения по пробелам
Самый простой и часто используемый способ — это применение std::istringstream в сочетании с оператором >>. Этот метод автоматически пропускает все пробельные символы (пробелы, табуляции, переводы строк) и читает слова до следующего пробела. Вот как это работает:
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::string text = "Hello, world! This is a test.";
std::istringstream stream(text);
std::string word;
while (stream >> word) {
std::cout << word << std::endl;
}
return 0;
}
Вывод:
Hello,
world!
This
is
a
test.
Важно: Этот метод сохраняет знаки препинания как часть слова. Если вам нужно удалить пунктуацию, придётся дополнительно обрабатывать каждое слово (например, с помощью std::remove_if).
Решение 2: Разбиение по произвольному набору разделителей (современный C++17)
Если вам нужно разбить строку по кастомным разделителям (например, запятым, точкам или точкам с запятой), можно использовать std::string_view и цикл с поиском. Этот метод эффективнее, так как не создаёт копии подстрок, а работает с представлением исходной строки.
#include <iostream>
#include <string>
#include <string_view>
#include <vector>
std::vector<std::string_view> split(std::string_view str, char delimiter) {
std::vector<std::string_view> tokens;
size_t start = 0;
size_t end = str.find(delimiter);
while (end != std::string_view::npos) {
tokens.push_back(str.substr(start, end - start));
start = end + 1;
end = str.find(delimiter, start);
}
tokens.push_back(str.substr(start));
return tokens;
}
int main() {
std::string text = "apple,banana,orange,grape";
auto words = split(text, ',');
for (auto word : words) {
std::cout << word << std::endl;
}
return 0;
}
Вывод:
apple
banana
orange
grape
Преимущества: Работает с любым разделителем, не копирует строки (использует string_view), легко адаптируется для нескольких разделителей (просто добавьте цикл по массиву разделителей).
Решение 3: Использование std::regex для сложных шаблонов (C++11)
Если разделители представляют собой не один символ, а регулярное выражение (например, любые знаки препинания или пробелы), то std::regex — ваш выбор. Однако будьте осторожны: регулярные выражения могут быть медленными для больших строк.
#include <iostream>
#include <regex>
#include <string>
#include <vector>
std::vector<std::string> split(const std::string& str, const std::regex& delim) {
std::sregex_token_iterator iter(str.begin(), str.end(), delim, -1);
std::sregex_token_iterator end;
return {iter, end};
}
int main() {
std::string text = "Hello, world! This is a test.";
std::regex delim("[\\s,;:.!?]+"); // разделители: пробелы, знаки препинания
auto words = split(text, delim);
for (const auto& word : words) {
if (!word.empty()) {
std::cout << word << std::endl;
}
}
return 0;
}
Вывод:
Hello
world
This
is
a
test
Рекомендация: Для большинства задач используйте первый или второй метод. Регулярные выражения применяйте только когда разделители сложные (например, "любая последовательность цифр" или "пробел, за которым не следует восклицательный знак").
Решение 4: Классический boost::split (если разрешены внешние библиотеки)
Библиотека Boost предоставляет мощный инструмент для работы со строками. boost::split поддерживает несколько разделителей и автоматически удаляет пустые токены.
#include <boost/algorithm/string.hpp>
#include <string>
#include <vector>
std::string text = "one,,two,three";
std::vector<std::string> tokens;
boost::split(tokens, text, boost::is_any_of(","), boost::token_compress_on);
// tokens = {"one", "two", "three"}
Примечание: boost::token_compress_on объединяет последовательные разделители в один, что избавляет от пустых строк. Если такого флага нет, то строка "one,,two" даст токены {"one", "", "two"}.
Альтернативный подход: Ручной парсинг с помощью std::find_if
Иногда хочется полного контроля над процессом. Можно написать функцию, которая вручную ищет начало и конец слова, используя std::find_if и лямбда-функцию для определения разделителей. Этот метод хорош тем, что не требует дополнительных библиотек и легко настраивается.
#include <iostream>
#include <string>
#include <algorithm>
#include <cctype>
std::vector<std::string> split_words(const std::string& str) {
std::vector<std::string> words;
auto it = str.begin();
while (it != str.end()) {
// Пропускаем разделители (пробелы, знаки препинания)
it = std::find_if(it, str.end(), [](char c) {
return !std::ispunct(c) && !std::isspace(c);
});
if (it == str.end()) break;
// Ищем конец слова
auto word_end = std::find_if(it, str.end(), [](char c) {
return std::ispunct(c) || std::isspace(c);
});
words.emplace_back(it, word_end);
it = word_end;
}
return words;
}
Плюсы: Полный контроль, легко добавить новые правила (например, не разбивать по дефису или апострофу). Минусы: Код длиннее, чем готовые решения, и может быть медленнее для коротких строк (но для большинства задач это не критично).
Совет: Для продакшена используйте std::istringstream или boost::split, а для учебных целей или специфических правил — ручной парсинг.