Полное руководство: как обрезать std::string в C++
Изучите надежные методы обрезки строк в C++ с обработкой граничных случаев, оптимизацией производительности и лучшими практиками удаления пробельных символов.
Как можно обрезать std::string в C++?
В настоящее время я использую следующий код для обрезки всех std::string справа в моих программах:
std::string s;
s.erase(s.find_last_not_of(" \n\r\t") + 1);
Это работает нормально, но я задумался, не существуют ли какие-то крайние случаи, где это может не сработать?
Ответы с элегантными альтернативами, а также решениями для обрезки слева приветствуются.
Обрезка std::strings в C++
Обрезка std::strings в C++ может быть выполнена несколькими способами, но ваш текущий подход имеет некоторые граничные случаи, которые могут вызвать неопределенное поведение. Основная проблема возникает, когда строка пуста или содержит только пробельные символы, так как find_last_not_of() возвращает std::string::npos (обычно -1), и добавление 1 может привести к неожиданным результатам.
Содержание
- Граничные случаи в вашем текущем подходе
- Решения для обрезки справа
- Решения для обрезки слева
- Комплексная обрезка (с обеих сторон)
- Лучшие практики и соображения по производительности
- Альтернативные подходы
Граничные случаи в вашем текущем подходе
Ваш текущий код для обрезки справа:
s.erase(s.find_last_not_of(" \n\r\t") + 1);
Имеет следующие проблемы:
-
Пустая строка: Когда
sпуста,find_last_not_of()возвращаетnpos(-1), поэтомуnpos + 1= 0, иerase(0)удаляет с позиции 0 до конца - это может сработать, но является ненадежным. -
Строка из пробельных символов: Та же проблема, что и с пустой строкой -
find_last_not_of()возвращаетnpos. -
Нет пробельных символов в конце: Это работает правильно, но код выполняется ненужно.
Более безопасный подход:
if (!s.empty()) {
s.erase(s.find_last_not_of(" \n\r\t") + 1);
}
Решения для обрезки справа
Базовая безопасная обрезка справа
void right_trim(std::string &s) {
if (!s.empty()) {
s.erase(s.find_last_not_of(" \n\r\t") + 1);
}
}
Надежная обрезка справа с настраиваемыми пробельными символами
void right_trim(std::string &s, const std::string &whitespace = " \n\r\t") {
const auto str_end = s.find_last_not_of(whitespace);
if (str_end != std::string::npos) {
s.erase(str_end + 1);
}
else {
// Строка состоит из пробельных символов, очищаем ее
s.clear();
}
}
Использование String View (C++17+)
#include <string_view>
std::string right_trim_copy(std::string_view s, std::string_view whitespace = " \n\r\t") {
const auto str_end = s.find_last_not_of(whitespace);
if (str_end != std::string_view::npos) {
return std::string(s.substr(0, str_end + 1));
}
return std::string();
}
Решения для обрезки слева
Базовая обрезка слева
void left_trim(std::string &s) {
s.erase(0, s.find_first_not_of(" \n\r\t"));
}
Надежная обрезка слева
void left_trim(std::string &s, const std::string &whitespace = " \n\r\t") {
const auto str_begin = s.find_first_not_of(whitespace);
if (str_begin != std::string::npos) {
s.erase(0, str_begin);
}
else {
// Строка состоит из пробельных символов, очищаем ее
s.clear();
}
}
Комплексная обрезка (с обеих сторон)
Простой комбинированный подход
void trim(std::string &s) {
left_trim(s);
right_trim(s);
}
Обрезка за один проход
void trim(std::string &s, const std::string &whitespace = " \n\r\t") {
const auto str_begin = s.find_first_not_of(whitespace);
if (str_begin == std::string::npos) {
// Строка состоит из пробельных символов или пуста
s.clear();
return;
}
const auto str_end = s.find_last_not_of(whitespace);
s = s.substr(str_begin, str_end - str_begin + 1);
}
Версия, возвращающая копию
std::string trim_copy(const std::string &s, const std::string &whitespace = " \n\r\t") {
const auto str_begin = s.find_first_not_of(whitespace);
if (str_begin == std::string::npos) {
return "";
}
const auto str_end = s.find_last_not_of(whitespace);
return s.substr(str_begin, str_end - str_begin + 1);
}
Лучшие практики и соображения по производительности
Универсальное решение на основе шаблонов
template<typename CharT>
basic_string<CharT> trim_copy(const basic_string<CharT> &str,
const basic_string<CharT> &whitespace = " \t\n\r\v\f") {
const auto str_begin = str.find_first_not_of(whitespace);
if (str_begin == basic_string<CharT>::npos)
return "";
const auto str_end = str.find_last_not_of(whitespace);
const auto str_range = str_end - str_begin + 1;
return str.substr(str_begin, str_range);
}
Версия с оптимизацией производительности
void trim_inplace(std::string &s) noexcept {
if (s.empty()) return;
auto start = s.begin();
while (start != s.end() && std::isspace(static_cast<unsigned char>(*start))) {
++start;
}
auto end = s.end();
do {
--end;
} while (std::distance(start, end) > 0 && std::isspace(static_cast<unsigned char>(*end)));
s = std::string(start, end + 1);
}
Использование <algorithm> и <cctype>
#include <algorithm>
#include <cctype>
void trim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
return !std::isspace(ch);
}));
s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
return !std::isspace(ch);
}).base(), s.end());
}
Альтернативные подходы
Использование String Views (C++17+)
#include <string_view>
std::string trim_copy(std::string_view s) {
const auto start = s.find_first_not_of(" \t\n\r\v\f");
if (start == std::string_view::npos) return "";
const auto end = s.find_last_not_of(" \t\n\r\v\f");
return std::string(s.substr(start, end - start + 1));
}
Использование лямбда-выражений
auto trim = [](std::string &s) {
s.erase(std::remove_if(s.begin(), s.end(), [](char c) {
return std::isspace(static_cast<unsigned char>(c));
}), s.end());
};
Примечание: Подход с лямбда-выражением выше удаляет ВСЕ пробельные символы, а не только ведущие и конечные. Для правильной обрезки используйте методы
find_first_not_of/find_last_not_of.
Алгоритмы строк Boost
Если вы используете Boost, вы можете использовать их алгоритмы строк:
#include <boost/algorithm/string.hpp>
// ...
boost::trim(s); // Обе стороны
boost::trim_left(s); // Только левая сторона
boost::trim_right(s); // Только правая сторона
Наиболее надежным решением для общего использования является шаблонная функция trim_copy, которая обрабатывает все граничные случаи, включая пустые строки, строки из пробельных символов и настраиваемые пробельные символы. Для критически важного по производительности кода рассмотрите итераторный подход, который избегает множественных выделений памяти для строк.