Как перебирать слова в строке на C++?
Мне нужно перебирать слова в строке, состоящей из слов, разделенных пробельными символами. Меня не интересуют функции для работы со строками в стиле C или манипуляции/доступ к символам. Я предпочитаю элегантность эффективности.
Вот мое текущее решение с использованием istringstream:
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main() {
string s = "Somewhere down the road";
istringstream iss(s);
do {
string subs;
iss >> subs;
cout << "Substring: " << subs << endl;
} while (iss);
}
Существуют ли более элегантные или альтернативные подходы для перебора слов в строке на C++?
Современный C++ предлагает несколько элегантных подходов для итерации по словам в строке помимо стандартного метода istringstream. К ним относятся использование string view из C++17 с алгоритмами, циклы на основе диапазонов с string view, а также мощная библиотека диапазонов C++20 для итерации в функциональном стиле. Каждый подход обеспечивает разный уровень элегантности и выразительности, сохраняя читаемость и избегая низкоуровневой манипуляции символами.
Содержание
- String Views с алгоритмами
- Циклы на основе диапазонов
- Библиотека диапазонов C++20
- Алгоритмы строк Boost
- Алгоритмы STL с пользовательскими предикатами
- Сравнение производительности
- Рекомендации
String Views с алгоритмами
String views (std::string_view), представленные в C++17, предоставляют элегантный способ итерации по словам без копирования данных. Этот подход обеспечивает лучшую производительность и более выразительный синтаксис:
#include <iostream>
#include <string>
#include <string_view>
#include <vector>
#include <algorithm>
std::vector<std::string_view> split_words(std::string_view text) {
std::vector<std::string_view> words;
auto start = text.begin();
auto end = text.begin();
while (end != text.end()) {
start = std::find_if_not(start, text.end(), [](char c) { return std::isspace(c); });
if (start == text.end()) break;
end = std::find_if(start, text.end(), [](char c) { return std::isspace(c); });
words.emplace_back(&*start, end - start);
start = end;
}
return words;
}
int main() {
std::string s = "Somewhere down the road";
auto words = split_words(s);
for (const auto& word : words) {
std::cout << "Word: " << word << std::endl;
}
}
Этот подход создает string views, ссылающиеся на память исходной строки, избегая ненужных копий при сохранении чистого и читаемого кода.
Циклы на основе диапазонов
Для более функционального подхода можно создать пользовательский итератор, который работает с циклами на основе диапазонов:
#include <iostream>
#include <string>
#include <cctype>
class WordIterator {
const std::string& str;
size_t pos = 0;
public:
WordIterator(const std::string& s) : str(s) {}
class Word {
const std::string& str;
size_t start, end;
public:
Word(const std::string& s, size_t b, size_t e) : str(s), start(b), end(e) {}
const std::string& operator*() const {
return str.substr(start, end - start);
}
// Другие методы итератора...
};
WordIterator begin() {
pos = str.find_first_not_of(" \t\n\r");
return *this;
}
WordIterator end() {
return WordIterator(str);
}
bool operator!=(const WordIterator& other) const {
return pos != other.pos;
}
WordIterator& operator++() {
pos = str.find_first_of(" \t\n\r", pos);
pos = str.find_first_not_of(" \t\n\r", pos);
return *this;
}
Word operator*() const {
auto end_pos = str.find_first_of(" \t\n\r", pos);
return Word(str, pos, end_pos);
}
};
int main() {
std::string s = "Somewhere down the road";
for (const auto& word : WordIterator(s)) {
std::cout << "Word: " << word << std::endl;
}
}
Этот подход обеспечивает наиболее естественный синтаксис и может быть повторно использован для разных строк.
Библиотека диапазонов C++20
Библиотека диапазонов C++20 предлагает наиболее элегантное и выразительное решение:
#include <iostream>
#include <string>
#include <ranges>
#include <vector>
int main() {
std::string s = "Somewhere down the road";
auto words = s
| std::views::split(' ')
| std::views::transform([](auto&& rng) {
return std::string_view(&*rng.begin(), std::ranges::distance(rng));
});
for (const auto& word : words) {
std::cout << "Word: " << word << std::endl;
}
}
Для более надежной реализации, обрабатывающей несколько символов пробела:
#include <iostream>
#include <string>
#include <ranges>
#include <vector>
#include <algorithm>
std::vector<std::string> split_words(const std::string& text) {
auto words = text
| std::views::split(' ')
| std::views::transform([](auto&& rng) {
return std::string(&*rng.begin(), std::ranges::distance(rng));
});
std::vector<std::string> result;
for (const auto& word : words) {
if (!word.empty()) {
result.push_back(word);
}
}
return result;
}
int main() {
std::string s = "Somewhere down the road";
auto words = split_words(s);
for (const auto& word : words) {
std::cout << "Word: " << word << std::endl;
}
}
Алгоритмы строк Boost
Если можно использовать внешние библиотеки, Boost предоставляет элегантные алгоритмы для работы со строками:
#include <iostream>
#include <string>
#include <boost/algorithm/string.hpp>
int main() {
std::string s = "Somewhere down the road";
std::vector<std::string> words;
boost::split(words, s, boost::is_space(),
boost::token_compress_on);
for (const auto& word : words) {
std::cout << "Word: " << word << std::endl;
}
}
Boost предлагает дополнительные опции для обработки разных типов пробельных символов и обрезки строк.
Алгоритмы STL с пользовательскими предикатами
Можно комбинировать алгоритмы STL с пользовательскими предикатами для элегантных решений:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cctype>
std::vector<std::string> split_words(const std::string& text) {
std::vector<std::string> words;
auto start = text.begin();
while (true) {
start = std::find_if(start, text.end(),
[](char c) { return !std::isspace(c); });
if (start == text.end()) break;
auto end = std::find_if(start, text.end(),
[](char c) { return std::isspace(c); });
words.emplace_back(start, end);
start = end;
}
return words;
}
int main() {
std::string s = "Somewhere down the road";
auto words = split_words(s);
for (const auto& word : words) {
std::cout << "Word: " << word << std::endl;
}
}
Сравнение производительности
| Подход | Производительность | Использование памяти | Читаемость | Стандарт C++ |
|---|---|---|---|---|
istringstream |
Умеренная | Высокое (копии) | Хорошая | C++98 |
| String Views | Отличная | Низкое (ссылки) | Хорошая | C++17 |
| Пользовательский итератор | Хорошая | Низкое | Умеренная | C++11 |
| Диапазоны C++20 | Отличная | Низкое | Отличная | C++20 |
| Boost | Хорошая | Умеренное | Отличная | Внешняя |
Рекомендации
Для современного C++ разработки я рекомендую:
- C++17+: Используйте string views с алгоритмами для наилучшего баланса между производительностью и элегантностью
- C++20: Если доступно, библиотека диапазонов предоставляет наиболее элегантное и выразительное решение
- Легаси-код: Подход с
istringstreamостается вполне приемлемым для простых случаев - Крупные проекты: Рассмотрите алгоритмы строк Boost для последовательных, хорошо протестированных решений
Наиболее элегантный подход зависит от вашего стандарта C++ и конкретных требований. Для максимальной элегантности и современного синтаксиса идеальны диапазоны C++20, в то время как string views предлагают отличную производительность с C++17.
Источники
- Документация C++17 std::string_view
- Обзор библиотеки диапазонов C++20
- Документация алгоритмов строк Boost
- Справочник по алгоритмам C++
- Обзор возможностей современного C++
Заключение
Современный C++ предлагает несколько элегантных подходов для итерации по словам в строке, каждый со своими преимуществами. Метод istringstream, который вы в настоящее время используете, вполне достаточен, но более новые подходы обеспечивают лучшую производительность и более выразительный синтаксис. Для наиболее элегантного решения рассмотрите использование диапазонов C++20 или string views C++17 в зависимости от поддержки вашим компилятором. Библиотека диапазонов предоставляет функциональный стиль композиции, в то время как string views предлагают отличную производительность с минимальными накладными расходами памяти. Выберите подход, который лучше всего соответствует стандарту C++ и требованиям производительности вашего проекта.