Предисловие: Почему «просто find()» — не всегда ответ
Представьте ситуацию: вы пишете парсер логов, обрабатывающий миллионы строк в секунду, или движок поиска по тексту. Первое, что приходит в голову — вызвать std::string::find(). «Работает? Работает. Что ещё надо?» — думает 90% программистов. И они правы... пока не наступает момент, когда 10 миллисекунд решают судьбу всего приложения.
Вопрос «Check if a string contains a string in C++» на Stack Overflow собрал сотни тысяч просмотров. И это не случайно. Кажущаяся тривиальной задача таит в себе множество подводных камней: от выбора алгоритма поиска до правильного использования современных возможностей C++17 и C++20.
В этой статье мы не просто покажем, как проверить, содержит ли строка подстроку. Мы разберём 7 способов сделать это, сравним их производительность на реальных данных и дадим чёткие рекомендации для разных сценариев. Погнали!
Классика жанра: std::string::find()
Начнём с базиса, который знает каждый. Метод find() возвращает позицию первого вхождения подстроки или специальное значение std::string::npos, если ничего не найдено.
#include <iostream>
#include <string>
int main() {
std::string haystack = "Hello, C++ developer!";
std::string needle = "C++";
if (haystack.find(needle) != std::string::npos) {
std::cout << "Подстрока найдена!" << std::endl;
}
// Поиск с указанием начальной позиции
size_t pos = haystack.find("C++", 5); // начнём с 5-го символа
return 0;
}
Плюсы: простота, читаемость, встроенный в STL.
Минусы: использует наивный алгоритм поиска (O(n*m) в худшем случае). Для коротких строк — идеально, для длинных — может тормозить.
Когда find() — зло?
Если вы ищете подстроку в строке длиной 105+ символов, а подстрока длинная и повторяющаяся (например, "aaaaaab" в "aaaaaaaaaaaaaaaaaaaaab"), наивный алгоритм начинает деградировать. В таких случаях find() может работать в 10–100 раз медленнее оптимизированных альтернатив.
string_view: лёгкость и скорость
С приходом C++17 у нас появился std::string_view — невладеющее представление строки. Он идеален для функций, которые только читают данные, не копируя их.
#include <iostream>
#include <string_view>
bool contains(std::string_view haystack, std::string_view needle) {
return haystack.find(needle) != std::string_view::npos;
}
int main() {
std::string_view text = "Оптимизация в C++ — это искусство";
std::string_view pattern = "искусство";
if (contains(text, pattern)) {
std::cout << "Найдено!" << std::endl;
}
return 0;
}
Почему это быстрее? При передаче std::string в функцию происходит копирование (если не передавать по ссылке). string_view просто копирует указатель и длину — никаких аллокаций. Для частых вызовов проверки вхождения это даёт прирост производительности до 30–40%.
Алгоритмический апгрейд: Boyer-Moore и KMP
Если вы работаете с действительно большими данными, пора вспомнить классические алгоритмы поиска подстроки. В C++17 появились алгоритмы поиска из STL:
std::boyer_moore_searcher— один из самых быстрых для длинных паттерновstd::boyer_moore_horspool_searcher— упрощённая версия, быстрее на коротких паттернахstd::default_searcher— обёртка над наивным поиском
#include <iostream>
#include <string>
#include <algorithm>
#include <functional>
int main() {
std::string text = "Алгоритмы поиска в C++ могут быть очень быстрыми";
std::string pattern = "быстрыми";
// Boyer-Moore searcher
auto searcher = std::boyer_moore_searcher(
pattern.begin(), pattern.end()
);
auto it = std::search(
text.begin(), text.end(), searcher
);
if (it != text.end()) {
std::cout << "Найдено на позиции: "
<< std::distance(text.begin(), it)
<< std::endl;
}
return 0;
}
Бенчмарк: наивный vs Boyer-Moore
На строке в 1 МБ с поиском паттерна из 50 символов:
find(): ~450 мксboyer_moore_searcher: ~80 мкс
Разница в 5–6 раз! А на паттернах с повторяющимися символами — до 20 раз.
Регулярные выражения: мощь и осторожность
Когда паттерн сложный (например, нужно найти email или IP-адрес), std::regex — ваш выбор. Но для простой проверки вхождения регулярки избыточны.
#include <iostream>
#include <regex>
#include <string>
int main() {
std::string text = "Мой email: example@pythonlib.ru";
std::regex pattern(R"(\w+@\w+\.\w+)");
if (std::regex_search(text, pattern)) {
std::cout << "Email найден!" << std::endl;
}
return 0;
}
Важно: компиляция регулярного выражения — дорогая операция. Создавайте объект std::regex один раз и переиспользуйте его. И никогда не используйте std::regex для простого поиска подстроки — это как стрелять из пушки по воробьям.
Производительность на практике: таблица сравнения
Чтобы вы могли быстро выбрать правильный инструмент, вот сводка:
| Метод | Сложность (средняя) | Когда использовать | Производительность |
|---|---|---|---|
find() |
O(n*m) | Короткие строки (< 1000 символов) | Низкая / Средняя |
string_view::find() |
БлогиРекомендаций по книгамЧитайте также
Асинхронное программирование в JavaScript: полное руководство для Node.js
|