std::string s;
s.erase(s.find_last_not_of(" \n\r\t")+1);
它运行良好,但我想知道是否存在一些极端情况会导致它失败?
当然,欢迎提供优雅的替代方案和左修剪解决方案。
#include <algorithm>
#include <cctype>
#include <locale>
// trim from start (in place)
static 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)
static 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());
}
// trim from both ends (in place)
static inline void trim(std::string &s) {
rtrim(s);
ltrim(s);
}
// trim from start (copying)
static inline std::string ltrim_copy(std::string s) {
ltrim(s);
return s;
}
// trim from end (copying)
static inline std::string rtrim_copy(std::string s) {
rtrim(s);
return s;
}
// trim from both ends (copying)
static inline std::string trim_copy(std::string s) {
trim(s);
return s;
}
我倾向于使用以下三种方法来满足我的修剪需求:
#include <algorithm>
#include <functional>
#include <cctype>
#include <locale>
// trim from start
static 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
static 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
static inline std::string &trim(std::string &s) {
return ltrim(rtrim(s));
}
它们相当易于理解,并且工作得非常好。
编辑:顺便提一下,我在这里使用了std::ptr_fun
来帮助消除std::isspace
的歧义,因为实际上有第二个支持本地化的定义。这也可以是一个转换,但我倾向于更喜欢这种方式。
编辑:针对一些评论关于通过引用接受参数、修改并返回它的问题。我同意。我可能更喜欢的实现方式是两组函数,一组用于原地操作,另一组则创建副本。更好的示例集将是:
#include <algorithm>
#include <functional>
#include <cctype>
#include <locale>
// trim from start (in place)
static 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)
static 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());
}
// trim from both ends (in place)
static inline void trim(std::string &s) {
rtrim(s);
ltrim(s);
}
// trim from start (copying)
static inline std::string ltrim_copy(std::string s) {
ltrim(s);
return s;
}
// trim from end (copying)
static inline std::string rtrim_copy(std::string s) {
rtrim(s);
return s;
}
// trim from both ends (copying)
static inline std::string trim_copy(std::string s) {
trim(s);
return s;
}
为了保留上下文和让最受欢迎的答案仍然可用,我将保留原始答案。
使用Boost字符串算法是最简单的方法:
#include <boost/algorithm/string.hpp>
std::string str("hello world! ");
boost::trim_right(str);
str
现在是"hello world!"
。还有trim_left
和trim
,它们会修剪字符串的两侧。
如果您将_copy
后缀添加到上述任何函数名称中,例如trim_copy
,函数将返回已修剪的字符串副本,而不是通过引用修改它。
如果您将_if
后缀添加到上述任何函数名称中,例如trim_copy_if
,则可以修剪满足您自定义谓词的所有字符,而不仅仅是空格。
#include <boost/format.hpp> <boost/tokenizer.hpp> <boost/lexical_cast.hpp>
,但是考虑到已经有基于 std::string::erase
的替代方案,我担心添加 <boost/algorithm/string.hpp>
会导致代码膨胀。不过很高兴地报告,在添加 Boost 的 trim 函数之前和之后比较 MinSizeRel 构建时,并没有增加我的代码大小(可能已经在其他地方使用了),而且我的代码也不会因为额外的一些函数而变得混乱。 - Rian Sanderson你所做的很好,也很可靠。我用了相同的方法已经有很长时间了,还没有找到一个更快的方法:
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);
}
试试这个,它对我有效。
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;
}
str.find_last_not_of(x)
函数返回第一个不等于x的字符位置。只有当没有任何字符与x不匹配时,它才会返回npos。在这个例子中,如果没有后缀空格,它将返回相当于str.length() - 1
,从而实际上是执行了str.erase((str.length() - 1) + 1)
。除非我完全错了。 - Travisstd::string&
更有意义。 - Galik使用以下代码可以从 std::strings
中右侧裁剪(尾随)空格和制表符 (ideone):
// trim trailing spaces
size_t endpos = str.find_last_not_of(" \t");
size_t startpos = str.find_first_not_of(" \t");
if( std::string::npos != endpos )
{
str = str.substr( 0, endpos+1 );
str = str.substr( startpos );
}
else {
str.erase(std::remove(std::begin(str), std::end(str), ' '), std::end(str));
}
为了平衡一下,我也会包括左侧修剪代码 (ideone):
// trim leading spaces
size_t startpos = str.find_first_not_of(" \t");
if( string::npos != startpos )
{
str = str.substr( startpos );
}
str.substr(...).swap(str)
更好,可以节省一次赋值操作。 - updogliubasic_string& operator= (basic_string&& str) noexcept;
? - nurettinresize()
来进行右侧修剪呢?它可能只涉及一个整数减少操作,这样就不会更便宜了... - Lightness Races in Orbit有点晚了,但没关系。现在有了C++11,我们有了Lambda和auto变量。我的版本还处理所有空格和空字符串:
#include <cctype>
#include <string>
#include <algorithm>
inline std::string trim(const std::string &s)
{
auto wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
auto wsback=std::find_if_not(s.rbegin(),s.rend(),[](int c){return std::isspace(c);}).base();
return (wsback<=wsfront ? std::string() : std::string(wsfront,wsback));
}
我们可以使用wsfront
构造一个反向迭代器,并将其用作第二个find_if_not
的终止条件,但这仅适用于完全由空格组成的字符串,而且至少在gcc 4.8中不够智能,无法使用auto
推断出反向迭代器的类型(std::string::const_reverse_iterator
)。我不知道构造反向迭代器的成本如何,所以结果可能因人而异。使用这种修改后,代码如下:
inline std::string trim(const std::string &s)
{
auto wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
return std::string(wsfront,std::find_if_not(s.rbegin(),std::string::const_reverse_iterator(wsfront),[](int c){return std::isspace(c);}).base());
}
std::isspace
函数:auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);
- vmrobisspace
有两个重载版本。此外,自C++20以来,在标准库中获取函数地址是未定义的行为。 - L. F.::isspace
函数可以使用(如果包含了 C 标准库头文件)。实际上,另一个问题是在将参数传递给 isspace 之前应该将其强制转换为 unsigned char,但这是另一个故事了。 - L. F.std::string trim(const std::string &s)
{
std::string::const_iterator it = s.begin();
while (it != s.end() && isspace(*it))
it++;
std::string::const_reverse_iterator rit = s.rbegin();
while (rit.base() != it && isspace(*rit))
rit++;
return std::string(it, rit.base());
}
it
),然后反转:在只有空格的字符之后的位置(rit
) - 然后它返回一个新创建的字符串 == 原始字符串的一部分的副本 - 该部分基于这些迭代器... - jave.web我喜欢tzaman的解决方案,唯一的问题是它不能删除只包含空格的字符串。
为了解决这个问题,在两行修剪代码之间添加str.clear()即可。
std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;
ltrim
或 rtrim
。 - tzamanstd::stringstream
中复制出来,所以速度相当慢。 - Galik使用C++17,您可以使用basic_string_view::remove_prefix和basic_string_view::remove_suffix:
std::string_view trim(std::string_view s)
{
s.remove_prefix(std::min(s.find_first_not_of(" \t\r\v\n"), s.size()));
s.remove_suffix(std::min(s.size() - s.find_last_not_of(" \t\r\v\n") - 1, s.size()));
return s;
}
一个不错的选择:
std::string_view ltrim(std::string_view s)
{
s.remove_prefix(std::distance(s.cbegin(), std::find_if(s.cbegin(), s.cend(),
[](int c) {return !std::isspace(c);})));
return s;
}
std::string_view rtrim(std::string_view s)
{
s.remove_suffix(std::distance(s.crbegin(), std::find_if(s.crbegin(), s.crend(),
[](int c) {return !std::isspace(c);})));
return s;
}
std::string_view trim(std::string_view s)
{
return ltrim(rtrim(s));
}
string::npos
的结果为 0。string::npos
是 string::size_type
类型,这是无符号类型。 因此,您依赖于加法溢出行为。std::string::npos
加1必须得到0。因此,可以绝对依赖这个假设。 - Galik
boost::trim
来解决这个问题。 - Tom