如何修剪标准::字符串



我目前正在使用以下代码来正确修剪程序中的所有std::strings

std::string s;
s.erase(s.find_last_not_of(" nrt")+1);

它工作正常,但我想知道是否有一些最终情况可能会失败?

当然,欢迎使用优雅的替代方案和左修剪解决方案的答案。

编辑 从 c++17 开始,标准库的某些部分被删除。幸运的是,从 c++11 开始,我们有 lambda,这是一个更好的解决方案。

#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;
}

感谢 https://stackoverflow.com/a/44973498/524503 提出现代解决方案。

原答案:

我倾向于使用以下 3 种之一来满足我的修剪需求:

#include <algorithm> 
#include <functional> 
#include <cctype>
#include <locale>
// trim from start
static inline std::string &ltrim(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_lefttrim,可以修剪两侧。


如果您_copy上述任何函数名称添加后缀,例如 trim_copy,该函数将返回字符串的修剪副本,而不是通过引用对其进行修改。

如果您_if上述任何函数名称添加后缀,例如 trim_copy_if,您可以修剪满足自定义谓词的所有字符,而不仅仅是空格。

你正在做的事情很好,很强大。我已经使用相同的方法很长时间了,但我还没有找到更快的方法:

const char* ws = " tnrfv";
// 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);
}
通过提供要修剪的字符,

您可以灵活地修剪非空格字符,并有效地仅修剪要修剪的字符。

使用以下

代码从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 );
}

试试这个,它对我有用。

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;
}

参加派对有点晚了,但没关系。现在 C++11 在这里,我们有 lambda 和自动变量。所以我的版本,也处理全空格和空字符串,是:

#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 至少不够聪明,无法推断反向迭代器的类型 ( std::string::const_reverse_iterator ) 与 auto 。我不知道构造反向迭代器有多昂贵,所以 YMMV 在这里。通过此更改,代码如下所示:

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());
}

http://ideone.com/nFVtEo

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());
}

我喜欢 tzaman 的解决方案,它唯一的问题是它不会修剪仅包含空格的字符串。

要纠正该 1 个缺陷,请在 2 个修剪器行之间添加一个 str.clear()

std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;

使用 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(" trvn"), s.size()));
    s.remove_suffix(std::min(s.size() - s.find_last_not_of(" trvn") - 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));
}

对于空字符串,代码假定将 1 加到 string::npos 得到 0。 string::npos 的类型为 string::size_type ,是无符号的。因此,您依赖于加法的溢出行为。

s.erase(0, s.find_first_not_of(" nrt"));                                                                                               
s.erase(s.find_last_not_of(" nrt")+1);   

被砍掉了 Cplusplus.com

std::string choppa(const std::string &t, const std::string &ws)
{
    std::string str = t;
    size_t found;
    found = str.find_last_not_of(ws);
    if (found != std::string::npos)
        str.erase(found+1);
    else
        str.clear();            // str is all whitespace
    return str;
}

这也适用于空情况。

str.erase(0, str.find_first_not_of("tnvfr ")); // left trim
str.erase(str.find_last_not_of("tnvfr ") + 1); // right trim

在线试用!

我的解决方案基于@Bill蜥蜴的答案。

请注意,如果输入字符串只包含空格,则这些函数将返回空字符串。

const std::string StringUtils::WHITESPACE = " nrt";
std::string StringUtils::Trim(const std::string& s)
{
    return TrimRight(TrimLeft(s));
}
std::string StringUtils::TrimLeft(const std::string& s)
{
    size_t startpos = s.find_first_not_of(StringUtils::WHITESPACE);
    return (startpos == std::string::npos) ? "" : s.substr(startpos);
}
std::string StringUtils::TrimRight(const std::string& s)
{
    size_t endpos = s.find_last_not_of(StringUtils::WHITESPACE);
    return (endpos == std::string::npos) ? "" : s.substr(0, endpos+1);
}

C++11还附带了一个正则表达式模块,当然可以用来修剪前导或尾随空格。

也许是这样的:

std::string ltrim(const std::string& s)
{
    static const std::regex lws{"^[[:space:]]*", std::regex_constants::extended};
    return std::regex_replace(s, lws, "");
}
std::string rtrim(const std::string& s)
{
    static const std::regex tws{"[[:space:]]*$", std::regex_constants::extended};
    return std::regex_replace(s, tws, "");
}
std::string trim(const std::string& s)
{
    return ltrim(rtrim(s));
}

我的答案是对这篇文章的最高答案的改进,该答案修剪了控制字符和空格(ASCII 表上的 0-32 和 127)。

std::isgraph确定字符是否具有图形表示形式,因此您可以使用它来更改 Evan 的答案,以从字符串的两侧删除任何没有图形表示形式的字符。 结果是一个更优雅的解决方案:

#include <algorithm>
#include <functional>
#include <string>
/**
 * @brief Left Trim
 *
 * Trims whitespace from the left end of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& ltrim(std::string& s) {
  s.erase(s.begin(), std::find_if(s.begin(), s.end(),
    std::ptr_fun<int, int>(std::isgraph)));
  return s;
}
/**
 * @brief Right Trim
 *
 * Trims whitespace from the right end of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& rtrim(std::string& s) {
  s.erase(std::find_if(s.rbegin(), s.rend(),
    std::ptr_fun<int, int>(std::isgraph)).base(), s.end());
  return s;
}
/**
 * @brief Trim
 *
 * Trims whitespace from both ends of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& trim(std::string& s) {
  return ltrim(rtrim(s));
}

注意:或者,如果您需要支持宽字符,您应该能够使用 std::iswgraph,但您还必须编辑此代码以启用std::wstring操作,这是我尚未测试的内容(请参阅参考页面以获取std::basic_string以探索此选项)。

这是使用正则表达式进行修剪的解决方案

#include <string>
#include <regex>
string trim(string str){
    return regex_replace(str, regex("(^[ ]+)|([ ]+$)"),"");
}

这是我使用的。 只要继续从前面移除空间,然后,如果还有什么,从后面做同样的事情。

void trim(string& s) {
    while(s.compare(0,1," ")==0)
        s.erase(s.begin()); // remove leading whitespaces
    while(s.size()>0 && s.compare(s.size()-1,1," ")==0)
        s.erase(s.end()-1); // remove trailing whitespaces
}

一种优雅的方式可以像

std::string & trim(std::string & str)
{
   return ltrim(rtrim(str));
}

支持功能实现为:

std::string & ltrim(std::string & str)
{
  auto it =  std::find_if( str.begin() , str.end() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
  str.erase( str.begin() , it);
  return str;   
}
std::string & rtrim(std::string & str)
{
  auto it =  std::find_if( str.rbegin() , str.rend() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
  str.erase( it.base() , str.end() );
  return str;   
}

一旦你把所有这些都准备好了,你也可以写这个:

std::string trim_copy(std::string const & str)
{
   auto s = str;
   return ltrim(rtrim(s));
}

修剪 C++11 实现:

static void trim(std::string &s) {
     s.erase(s.begin(), std::find_if_not(s.begin(), s.end(), [](char c){ return std::isspace(c); }));
     s.erase(std::find_if_not(s.rbegin(), s.rend(), [](char c){ return std::isspace(c); }).base(), s.end());
}

我想如果你开始要求修剪字符串的"最佳方法",我会说一个好的实现将是:

  1. 不分配临时字符串
  2. 具有用于就地修剪和复制修剪的重载
  3. 可以轻松定制以接受不同的验证序列/逻辑

显然,有太多不同的方法可以解决这个问题,这绝对取决于您的实际需求。但是,C 标准库在 中仍然有一些非常有用的函数,比如 memchr。C 仍然被认为是 IO 的最佳语言是有原因的 - 它的 stdlib 是纯粹的效率。

inline const char* trim_start(const char* str)
{
    while (memchr(" tnr", *str, 4))  ++str;
    return str;
}
inline const char* trim_end(const char* end)
{
    while (memchr(" tnr", end[-1], 4)) --end;
    return end;
}
inline std::string trim(const char* buffer, int len) // trim a buffer (input?)
{
    return std::string(trim_start(buffer), trim_end(buffer + len));
}
inline void trim_inplace(std::string& str)
{
    str.assign(trim_start(str.c_str()),
        trim_end(str.c_str() + str.length()));
}
int main()
{
    char str [] = "t nhellor t n";
    string trimmed = trim(str, strlen(str));
    cout << "'" << trimmed << "'" << endl;
    system("pause");
    return 0;
}

对于它的价值,这是一个着眼于性能的修剪实现。 它比我见过的许多其他修剪程序要快得多。 它不使用迭代器和 std::finds,而是使用原始 c 字符串和索引。 它优化了以下特殊情况:大小 0 字符串(不执行任何操作)、没有要修剪的空格的字符串(什么都不执行)、只有尾随空格的字符串(只需调整字符串大小)、完全是空格的字符串(只需清除字符串)。 最后,在最坏的情况下(带有前导空格的字符串),它会尽力执行有效的复制构造,只执行 1 个副本,然后移动该副本代替原始字符串。

void TrimString(std::string & str)
{ 
    if(str.empty())
        return;
    const auto pStr = str.c_str();
    size_t front = 0;
    while(front < str.length() && std::isspace(int(pStr[front]))) {++front;}
    size_t back = str.length();
    while(back > front && std::isspace(int(pStr[back-1]))) {--back;}
    if(0 == front)
    {
        if(back < str.length())
        {
            str.resize(back - front);
        }
    }
    else if(back <= front)
    {
        str.clear();
    }
    else
    {
        str = std::move(std::string(str.begin()+front, str.begin()+back));
    }
}
由于

添加了back()pop_back(),这可以在 C++11 中更简单地完成。

while ( !s.empty() && isspace(s.back()) ) s.pop_back();

为噪音贡献我的解决方案。 trim默认创建新字符串并返回修改后的字符串,同时trim_in_place修改传递给它的字符串。trim函数支持 c++11 移动语义。

#include <string>
// modifies input string, returns input
std::string& trim_left_in_place(std::string& str) {
    size_t i = 0;
    while(i < str.size() && isspace(str[i])) { ++i; };
    return str.erase(0, i);
}
std::string& trim_right_in_place(std::string& str) {
    size_t i = str.size();
    while(i > 0 && isspace(str[i - 1])) { --i; };
    return str.erase(i, str.size());
}
std::string& trim_in_place(std::string& str) {
    return trim_left_in_place(trim_right_in_place(str));
}
// returns newly created strings
std::string trim_right(std::string str) {
    return trim_right_in_place(str);
}
std::string trim_left(std::string str) {
    return trim_left_in_place(str);
}
std::string trim(std::string str) {
    return trim_left_in_place(trim_right_in_place(str));
}
#include <cassert>
int main() {
    std::string s1(" trn  ");
    std::string s2("  rnc");
    std::string s3("c t");
    std::string s4("  rc ");
    assert(trim(s1) == "");
    assert(trim(s2) == "c");
    assert(trim(s3) == "c");
    assert(trim(s4) == "c");
    assert(s1 == " trn  ");
    assert(s2 == "  rnc");
    assert(s3 == "c t");
    assert(s4 == "  rc ");
    assert(trim_in_place(s1) == "");
    assert(trim_in_place(s2) == "c");
    assert(trim_in_place(s3) == "c");
    assert(trim_in_place(s4) == "c");
    assert(s1 == "");
    assert(s2 == "c");
    assert(s3 == "c");
    assert(s4 == "c");  
}

这是我想到的:

std::stringstream trimmer;
trimmer << str;
trimmer >> str;

流提取会自动消除空格,因此这就像一个魅力。
也相当干净优雅,如果我自己这么说的话。;)

我不确定您的环境是否相同,但在我的环境中,空字符串大小写会导致程序中止。我要么用if(!s.empty())包装该擦除调用,要么使用已经提到的Boost。

这是我

的版本:

size_t beg = s.find_first_not_of(" rn");
return (beg == string::npos) ? "" : in.substr(beg, s.find_last_not_of(" rn") - beg);
对于

不习惯到处写std::并且还不熟悉const正确性、iterator s、STL algorithm s 等的初学者来说,这是一个易于理解的解决方案......

#include <string>
#include <cctype> // for isspace
using namespace std;

// Left trim the given string ("  hello!  " --> "hello!  ")
string left_trim(string str) {
    int numStartSpaces = 0;
    for (int i = 0; i < str.length(); i++) {
        if (!isspace(str[i])) break;
        numStartSpaces++;
    }
    return str.substr(numStartSpaces);
}
// Right trim the given string ("  hello!  " --> "  hello!")
string right_trim(string str) {
    int numEndSpaces = 0;
    for (int i = str.length() - 1; i >= 0; i--) {
        if (!isspace(str[i])) break;
        numEndSpaces++;
    }
    return str.substr(0, str.length() - numEndSpaces);
}
// Left and right trim the given string ("  hello!  " --> "hello!")
string trim(string str) {
    return right_trim(left_trim(str));
}

希望对你有帮助...

上述方法很棒,但有时您希望将函数组合用于例程认为是空格的内容。 在这种情况下,使用函子组合操作可能会变得混乱,所以我更喜欢一个简单的循环,我可以修改修剪。 这是一个稍微修改的修剪功能,从SO上的C版本复制而来。 在此示例中,我正在修剪非字母数字字符。

string trim(char const *str)
{
  // Trim leading non-letters
  while(!isalnum(*str)) str++;
  // Trim trailing non-letters
  end = str + strlen(str) - 1;
  while(end > str && !isalnum(*end)) end--;
  return string(str, end+1);
}

这个呢...?

#include <iostream>
#include <string>
#include <regex>
std::string ltrim( std::string str ) {
    return std::regex_replace( str, std::regex("^\s+"), std::string("") );
}
std::string rtrim( std::string str ) {
    return std::regex_replace( str, std::regex("\s+$"), std::string("") );
}
std::string trim( std::string str ) {
    return ltrim( rtrim( str ) );
}
int main() {
    std::string str = "   t  this is a test string  n   ";
    std::cout << "-" << trim( str ) << "-n";
    return 0;
}

注意:我对C++还比较陌生,所以如果我在这里偏离了基础,请原谅我。

相关内容

  • 没有找到相关文章

最新更新