首页 文章

修剪std :: string的最佳方法是什么?

提问于
浏览
676

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

std::string s;
s.erase(s.find_last_not_of(" \n\r\t")+1);

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

当然,欢迎使用优雅替代品和左侧修饰解决方案的答案 .

30 回答

  • 8

    由于添加了back()pop_back(),因此可以在C 11中更简单地完成此操作 .

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

    我的解决方案基于answer by @Bill the Lizard .

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

    const std::string StringUtils::WHITESPACE = " \n\r\t";
    
    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);
    }
    
  • 5

    EDIT 从第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(), [](int 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(), [](int ch) {
            return !std::isspace(ch);
        }).base(), s.end());
    }
    
    // trim from both ends (in place)
    static inline void trim(std::string &s) {
        ltrim(s);
        rtrim(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提出现代解决方案 .

    原始答案:

    我倾向于使用这些中的一个来满足我的修剪需求:

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

    他们相当自我解释,工作得很好 .

    EDIT :顺便说一句,我在那里有 std::ptr_fun 来帮助消除 std::isspace 的歧义,因为实际上有第二个定义支持语言环境 . 这可能是一个演员也一样,但我倾向于更喜欢这个 .

    EDIT :解决有关通过引用接受参数,修改并返回参数的一些注释 . 我同意 . 我可能更喜欢的实现是两组函数,一组用于就地,另一组用于复制 . 一组更好的例子是:

    #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) {
        ltrim(s);
        rtrim(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;
    }
    

    我保留上面的原始答案,但是为了保持高投票的答案仍然可用 .

  • 57

    我不确定你的环境是否相同,但在我的情况下,空字符串大小写会导致程序中止 . 我会用if(!s.empty())包装那个erase调用,或者如前所述使用Boost .

  • 7

    这是我的版本:

    size_t beg = s.find_first_not_of(" \r\n");
    return (beg == string::npos) ? "" : in.substr(beg, s.find_last_not_of(" \r\n") - beg);
    
  • 50

    对于它的 Value ,这里是一个关注性能的修剪实现 . 它比我见过的许多其他修剪程序快得多 . 它使用原始c字符串和索引,而不是使用迭代器和std :: finds . 它优化了以下特殊情况:size 0 string(什么都不做),没有要修剪的空格的字符串(什么也不做),只有尾随空格的字符串要修剪(只调整字符串大小),字符串完全是空格(只是清除字符串) . 最后,在最坏的情况下(带有前导空格的字符串),它会尽最大努力执行有效的复制构造,只执行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));
        }
    }
    
  • 1

    上面的方法很棒,但有时候你想要使用函数的组合来实现你的例程所认为的空格 . 在这种情况下,使用仿函数来组合操作会变得混乱,所以我更喜欢一个简单的循环,我可以修改为修剪 . 这是在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);
    }
    
  • 2

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

    使用以下代码从 std::stringsideone)右侧修剪(尾随)空格和制表符:

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

    只是为了 balancer 问题,我也会包含左边的修剪代码(ideone):

    // trim leading spaces
    size_t startpos = str.find_first_not_of(" \t");
    if( string::npos != startpos )
    {
        str = str.substr( startpos );
    }
    
  • 31

    这样做的优雅方式可能就像

    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));
    }
    
  • 4

    试试这个,它对我有用 .

    inline std::string trim(std::string& str)
    {
        str.erase(0, str.find_first_not_of(' '));       //prefixing spaces
        str.erase(str.find_last_not_of(' ')+1);         //surfixing spaces
        return str;
    }
    
  • 18

    由于我想用C 11方法更新我的旧C修剪功能,我已经测试了很多已发布的问题答案 . 我的结论是我保留了旧的C解决方案!

    它是最快的一个,甚至添加更多的字符来检查(例如\ r \ n我看到没有用于\ f \ v的用例)仍然比使用算法的解决方案更快 .

    std::string & trimMe (std::string & str)
         {
            // right trim
            while (str.length () > 0 && (str [str.length ()-1] == ' ' || str [str.length ()-1] == '\t'))
               str.erase (str.length ()-1, 1);
    
            // left trim
            while (str.length () > 0 && (str [0] == ' ' || str [0] == '\t'))
               str.erase (0, 1);
            return str;
         }
    
  • 3

    此版本修剪内部空白和非字母数字:

    static inline std::string &trimAll(std::string &s)
    {   
        if(s.size() == 0)
        {
            return s;
        }
    
        int val = 0;
        for (int cur = 0; cur < s.size(); cur++)
        {
            if(s[cur] != ' ' && std::isalnum(s[cur]))
            {
                s[val] = s[cur];
                val++;
            }
        }
        s.resize(val);
        return s;
    }
    
  • 13

    在空字符串的情况下,您的代码假定向 string::npos 添加1给出了0. string::nposstring::size_type 类型,它是无符号的 . 因此,您依赖于添加的溢出行为 .

  • 3

    被黑客攻击Cplusplus.com

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

    这适用于null情况 . :-)

  • 386

    c 11:

    int i{};
    string s = " h e ll \t\n  o";
    string trim = " \n\t";
    
    while ((i = s.find_first_of(trim)) != -1)
        s.erase(i,1);
    
    cout << s;
    

    输出:

    hello
    

    使用空字符串也可以正常工作

  • 35

    为噪音贡献我的解决方案 . 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(" \t\r\n  ");
        std::string s2("  \r\nc");
        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 == " \t\r\n  ");
        assert(s2 == "  \r\nc");
        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");  
    }
    
  • 25

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

    • 不分配临时字符串

    • 具有就地修剪和复制修剪的重载

    • 可以轻松定制以接受不同的验证序列/逻辑

    显然,有太多不同的方法可以解决这个问题,这绝对取决于你实际需要什么 . 但是,C标准库在<string.h>中仍然有一些非常有用的函数,就像memchr一样 . 有一个原因使C仍然被认为是IO的最佳语言 - 它的stdlib是纯粹的效率 .

    inline const char* trim_start(const char* str)
    {
        while (memchr(" \t\n\r", *str, 4))  ++str;
        return str;
    }
    inline const char* trim_end(const char* end)
    {
        while (memchr(" \t\n\r", 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 \nhello\r \t \n";
    
        string trimmed = trim(str, strlen(str));
        cout << "'" << trimmed << "'" << endl;
    
        system("pause");
        return 0;
    }
    
  • 551

    随着C 11也出现了regular expression模块,当然可以用来修剪前导或尾随空格 .

    也许是这样的:

    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));
    }
    
  • 4

    这就是我使用的 . 只需继续从前面移除空间,然后,如果还剩下任何东西,请从后面做同样的事情 .

    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
    }
    
  • 6

    使用Boost's string algorithms将是最简单的:

    #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 ,您可以修剪满足自定义谓词的所有字符,而不仅仅是空格 .

  • 6

    我的回答是对这篇文章的top answer进行了改进,修改了控制字符和空格(ASCII table上的0-32和127) .

    std::isgraph确定角色是否具有图形表示,因此您可以使用它来改变Evan 's answer to remove any character that doesn' t从字符串的任一侧获得图形表示 . 结果是一个更优雅的解决方案:

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

    Note: 或者你应该能够使用std::iswgraph,如果你需要支持宽字符,但是你还必须编辑这段代码来启用 std::wstring 操作,这是我没有测试的东西(参见std::basic_string的参考页面来探索这个选项) .

  • 7

    这就是我想出的:

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

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

  • 1

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

    要纠正1个缺陷,请在2条修剪线之间添加str.clear()

    std::stringstream trimmer;
    trimmer << str;
    str.clear();
    trimmer >> str;
    
  • 1
    s.erase(0, s.find_first_not_of(" \n\r\t"));                                                                                               
    s.erase(s.find_last_not_of(" \n\r\t")+1);
    
  • 3

    修剪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());
    }
    
  • 3

    另一种选择 - 从两端删除一个或多个字符 .

    string strip(const string& s, const string& chars=" ") {
        size_t begin = 0;
        size_t end = s.size()-1;
        for(; begin < s.size(); begin++)
            if(chars.find_first_of(s[begin]) == string::npos)
                break;
        for(; end > begin; end--)
            if(chars.find_first_of(s[end]) == string::npos)
                break;
        return s.substr(begin, end-begin+1);
    }
    
  • 15

    派对迟到了,但没关系 . 现在C 11在这里,我们有lambdas和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 中的终止条件,但's only useful in the case of an all-whitespace string, and gcc 4.8 at least isn' t足够聪明,可以用 auto 推断反向迭代器( std::string::const_reverse_iterator )的类型 . 我不知道构造反向迭代器有多贵,所以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());
    }
    
  • 1

    你在做什么是好的和强大的 . 我已经使用了相同的方法很长一段时间,我还没有找到一个更快的方法:

    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 (left & right)
    inline std::string& trim(std::string& s, const char* t = ws)
    {
        return ltrim(rtrim(s, t), t);
    }
    

    通过提供要修剪的字符,您可以灵活地修剪非空白字符,并且只能修剪您想要修剪的字符 .

  • 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,所以如果我不在这里,请原谅我 .

相关问题