¿Cómo recortar un std::string?
Actualmente estoy usando el siguiente código para recortar a la derecha todos los std::string
s en mis programas:
std::string s;
s.erase(s.find_last_not_of(" \n\r\t")+1);
Funciona bien, pero me pregunto si hay algunos casos extremos en los que podría fallar.
Por supuesto, se aceptan respuestas con alternativas elegantes y también una solución de recorte a la izquierda.
Nueva respuesta para C++11
#include <algorithm>
#include <cctype>
#include <locale>
// trim from start (in place)
inline void ltrim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
}
// trim from end (in place)
inline void rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), s.end());
}
Gracias a https://stackoverflow.com/a/44973498/524503 por presentar la solución moderna.
Funciones de conveniencia
// trim from both ends (in place)
inline void trim(std::string &s) {
rtrim(s);
ltrim(s);
}
// trim from start (copying)
inline std::string ltrim_copy(std::string s) {
ltrim(s);
return s;
}
// trim from end (copying)
inline std::string rtrim_copy(std::string s) {
rtrim(s);
return s;
}
// trim from both ends (copying)
inline std::string trim_copy(std::string s) {
trim(s);
return s;
}
Respuesta actualizada para C++03
Para abordar algunos comentarios sobre aceptar un parámetro por referencia, modificarlo y devolverlo. Estoy de acuerdo. Una implementación que probablemente preferiría sería dos conjuntos de funciones, una para implementar y otra para realizar una copia. Un mejor conjunto de ejemplos sería:
#include <algorithm>
#include <functional>
#include <cctype>
#include <locale>
// trim from start (in place)
inline void ltrim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
std::not1(std::ptr_fun<int, int>(std::isspace))));
}
// trim from end (in place)
inline void rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(),
std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
}
// the remaining functions (trim() et al.) are identical to the new C++11 version
std::ptr_fun
es necesario desambiguarstd::isspace
porque hay una segunda definición que admite configuraciones regionales. Esto podría haber sido un elenco de todos modos, pero esto me gusta más.
Respuesta original para C++03
Mantengo esta respuesta original para el contexto y con el fin de mantener disponible la respuesta más votada.
#include <algorithm>
#include <functional>
#include <cctype>
#include <locale>
// trim from start
inline std::string <rim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
std::not1(std::ptr_fun<int, int>(std::isspace))));
return s;
}
// trim from end
inline std::string &rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(),
std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
return s;
}
// trim from both ends
inline std::string &trim(std::string &s) {
return ltrim(rtrim(s));
}
Usar los algoritmos de cadenas de Boost sería lo más fácil:
#include <boost/algorithm/string.hpp>
std::string str("hello world! ");
boost::trim_right(str);
str
es ahora "hello world!"
. También está trim_left
y trim
, que recorta ambos lados.
Si agrega _copy
un sufijo a cualquiera de los nombres de funciones anteriores, por ejemplo trim_copy
, la función devolverá una copia recortada de la cadena en lugar de modificarla mediante una referencia.
Si agrega _if
un sufijo a cualquiera de los nombres de funciones anteriores, por ejemplo trim_copy_if
, puede recortar todos los caracteres que satisfagan su predicado personalizado, en lugar de solo espacios en blanco.
Lo que estás haciendo es bueno y robusto. He usado el mismo método durante mucho tiempo y todavía tengo que encontrar un método más rápido:
const char* ws = " \t\n\r\f\v";
// trim from end of string (right)
inline std::string& rtrim(std::string& s, const char* t = ws)
{
s.erase(s.find_last_not_of(t) + 1);
return s;
}
// trim from beginning of string (left)
inline std::string& ltrim(std::string& s, const char* t = ws)
{
s.erase(0, s.find_first_not_of(t));
return s;
}
// trim from both ends of string (right then left)
inline std::string& trim(std::string& s, const char* t = ws)
{
return ltrim(rtrim(s, t), t);
}
Al proporcionar los caracteres que se van a recortar, tiene la flexibilidad de recortar caracteres que no sean espacios en blanco y la eficiencia de recortar solo los caracteres que desea recortar.
Prueba esto, a mí me funciona.
inline std::string trim(std::string& str)
{
str.erase(str.find_last_not_of(' ')+1); //suffixing spaces
str.erase(0, str.find_first_not_of(' ')); //prefixing spaces
return str;
}