Как преобразовать std::string в нижний регистр в C++?
Правильный способ: std::transform с std::tolower
Ваш цикл в целом рабочий, но есть несколько нюансов, которые стоит учесть. Главная проблема — std::tolower принимает int, который должен быть представлен как unsigned char (или EOF). Если char у вас знаковый (signed), то при передаче символа с кодом > 127 (например, буквы «ё» в Windows-1251) может произойти отрицательное значение, что приведёт к неопределённому поведению (UB).
Вот правильное и идиоматичное решение на C++11 и новее:
#include <algorithm>
#include <cctype>
#include <string>
std::string to_lower(const std::string& str) {
std::string result = str;
std::transform(result.begin(), result.end(), result.begin(),
[](unsigned char c) { return std::tolower(c); });
return result;
}
Или, если хотите изменить строку на месте:
void to_lower_inplace(std::string& str) {
std::transform(str.begin(), str.end(), str.begin(),
[](unsigned char c) { return std::tolower(c); });
}
Разбор решения
- std::transform — алгоритм из <algorithm>, который применяет функцию к каждому элементу диапазона и записывает результат в выходной диапазон. Третий аргумент может совпадать с первым (изменяем на месте).
- Лямбда-функция принимает
unsigned char— это ключевой момент. Мы явно приводимcharкunsigned char, чтобы избежать UB при передаче вstd::tolower. - std::tolower из <cctype> корректно работает только с неотрицательными значениями. Приведение к
unsigned charрешает эту проблему.
Альтернативы для разных версий C++
C++20 и новее — используйте ranges:
#include <ranges>
#include <cctype>
std::string to_lower(const std::string& str) {
return str | std::views::transform([](unsigned char c) {
return std::tolower(c);
}) | std::ranges::to<std::string>();
}
C++17 — можно использовать std::string_view для входного параметра, чтобы избежать копирования:
std::string to_lower(std::string_view str) {
std::string result;
result.reserve(str.size());
for (unsigned char c : str) {
result.push_back(std::tolower(c));
}
return result;
}
Для работы с Unicode (русские буквы, UTF-8)
Если ваша строка содержит кириллицу в UTF-8, std::tolower не подойдёт — он работает только с ASCII. Вам понадобится библиотека вроде ICU или Boost.Locale:
#include <boost/locale.hpp>
std::string to_lower_utf8(const std::string& str) {
return boost::locale::to_lower(str, std::locale("ru_RU.UTF-8"));
}
Производительность
Если вы вызываете преобразование очень часто (миллионы строк), рассмотрите ручной цикл без лямбды — он может быть немного быстрее за счёт меньшего количества косвенных вызовов. Но для 99% случаев std::transform — лучший выбор по читаемости и надёжности.
В итоге: используйте std::transform с приведением к unsigned char — это стандартный, безопасный и эффективный способ преобразования std::string в нижний регистр в C++.
Ручной цикл с явным приведением типа
Если вы предпочитаете не использовать std::transform или работаете в среде, где <algorithm> недоступен (например, в embedded-разработке), можно обойтись простым циклом. Главное — не забыть про приведение к unsigned char:
#include <string>
#include <cctype>
void to_lower_inplace(std::string& str) {
for (char& ch : str) {
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
}
}
Этот код работает так же, как и версия с std::transform, но без дополнительных накладных расходов на вызов алгоритма. Для коротких строк разница незаметна, но в цикле с миллионами итераций ручной вариант может быть на 5-10% быстрее.
Недостаток: если вы хотите сохранить исходную строку, придётся вручную копировать её перед вызовом. Для этого удобнее использовать версию, возвращающую новую строку:
std::string to_lower_copy(const std::string& str) {
std::string result = str;
to_lower_inplace(result);
return result;
}
В целом, оба подхода корректны. Выбирайте тот, который вам интуитивно понятнее и лучше вписывается в стиль вашего кода.