如何截取一个std::string?

1001
我目前在我的程序中使用以下代码来去除所有的std::string的右空格:
std::string s;
s.erase(s.find_last_not_of(" \n\r\t")+1);

它运行良好,但我想知道是否存在一些极端情况会导致它失败?
当然,欢迎提供优雅的替代方案和左修剪解决方案。
52个回答

7

以下是一份性能优化的字符串修剪实现。与我看到的许多其他修剪例程相比,它要快得多。它不使用迭代器和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));
    }
}

@bmgda 或许从理论上讲,最快的版本可能是具有这个签名:extern "C" void string_trim ( char ** begin_, char ** end_ ) ... 你明白我的意思吗? - user10133158

7

由于C++11增加了back()pop_back(),因此可以更简单地完成此操作。

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

OP提出的方法也不错,只是有点难以理解。 - Brent Bradburn

7
我想如果你开始询问“最佳方法”来修剪字符串,我会说一个好的实现应该是:
  1. 不分配临时字符串
  2. 具有就地修剪和复制修剪的重载
  3. 可以轻松自定义以接受不同的验证序列/逻辑
显然,有太多不同的方法来解决这个问题,它肯定取决于你实际需要什么。然而,C标准库仍然在中拥有一些非常有用的函数,比如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;
}

6

为了贡献我的解决方案,trim默认会创建一个新字符串并返回修改后的结果,而trim_in_place则是直接修改传入的字符串。这两个函数都支持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");  
}

5
这是我想到的内容:
std::stringstream trimmer;
trimmer << str;
trimmer >> str;

流提取自动消除空格,所以这个功能非常好用。如果我说得好听些的话,它还相当干净和优雅。 ;)

17
这里的前提是字符串内部没有空格(例如空格符)。原帖仅指出他想要修剪左侧或右侧的空格。 - SuperElectric

3

这里是我的版本:

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

你缺少了最后一个字符。在长度上加1可以解决这个问题。 - galinette

3

我不确定你的环境是否相同,但在我的环境中,空字符串会导致程序中止。我建议您将erase调用包装在if(!s.empty())中或使用已经提到的Boost库。


3
这里有一个简单易懂的解决方案,适合初学者不习惯到处写“std::”,还不熟悉“const”正确性、迭代器、STL算法等等...
#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));
}

希望这能有所帮助...

2

这里是一个直接的实现方法。对于如此简单的操作,您可能不应该使用任何特殊结构。内置的isspace()函数可以处理各种形式的空白字符,因此我们应该利用它。您还必须考虑字符串为空或仅是一堆空格的特殊情况。修剪左侧或右侧可以从以下代码中派生。

string trimSpace(const string &str) {
   if (str.empty()) return str;
   string::size_type i,j;
   i=0;
   while (i<str.size() && isspace(str[i])) ++i;
   if (i == str.size())
      return string(); // empty string
   j = str.size() - 1;
   //while (j>0 && isspace(str[j])) --j; // the j>0 check is not needed
   while (isspace(str[j])) --j
   return str.substr(i, j-i+1);
}

2

您可以使用此函数来在C++中修剪字符串。

void trim(string& str){
   while(str[0] == ' ') str.erase(str.begin());
   while(str[str.size() - 1] == ' ') str.pop_back();
}

如果使用C++17,可以考虑将str[str.size() - 1]替换为str.back()。但更大的问题是,在空字符串或全空格字符串上,这会产生未定义的行为。 - ggorlen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接