Какой лучший способ обрезать std :: string? - PullRequest
722 голосов
/ 19 октября 2008

В настоящее время я использую следующий код, чтобы обрезать вправо все std::strings в моих программах:

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

Работает нормально, но мне интересно, есть ли какие-нибудь конечные случаи, когда он может потерпеть неудачу?

Конечно, ответы с изящными альтернативами, а также с левым решением приветствуются.

Ответы [ 40 ]

10 голосов
/ 25 мая 2013

Мое решение основано на ответе @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);
}
9 голосов
/ 21 марта 2015

Мой ответ является улучшением верхнего ответа для этого поста, который обрезает управляющие символы, а также пробелы (0-32 и 127 в таблице ASCII ).

std::isgraph определяет, имеет ли символ графическое представление, так что вы можете использовать это, чтобы изменить ответ Эвана, чтобы удалить любой символ, который не имеет графического представления с любой стороны строки. В результате получается гораздо более элегантное решение:

#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, чтобы изучить эту опцию).

7 голосов
/ 13 ноября 2013

Это то, что я использую. Просто продолжайте убирать пространство спереди, а затем, если что-то осталось, сделайте то же самое сзади.

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
}
7 голосов
/ 27 августа 2013

С 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));
}
7 голосов
/ 06 января 2015

Для того, чтобы это стоило, вот аккуратная реализация с оглядкой на производительность. Это намного быстрее, чем многие другие процедуры обрезки, которые я видел вокруг. Вместо использования итераторов и std :: find он использует необработанные строки и индексы 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));
    }
}
7 голосов
/ 13 октября 2015
s.erase(0, s.find_first_not_of(" \n\r\t"));                                                                                               
s.erase(s.find_last_not_of(" \n\r\t")+1);   
6 голосов
/ 18 августа 2015

Элегантный способ сделать это может быть как

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));
}
5 голосов
/ 14 октября 2015

Реализация Trim 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());
}
4 голосов
/ 17 ноября 2013

Полагаю, если вы начнете спрашивать «лучший способ» обрезать строку, я бы сказал, что хорошей реализацией будет такая, которая:

  1. Не выделяет временные строки
  2. Имеет перегрузки для обрезки на месте и копирования обрезки
  3. Может быть легко настроен для принятия различных последовательностей проверки / логики

Очевидно, что существует слишком много разных способов подойти к этому, и это определенно зависит от того, что вам действительно нужно. Однако стандартная библиотека C по-прежнему имеет несколько очень полезных функций в , таких как memchr. Есть причина, по которой C до сих пор считается лучшим языком для ввода-вывода - его 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;
}
4 голосов
/ 25 января 2019

С C ++ 17 вы можете использовать basic_string_view :: remove_prefix и basic_string_view :: remove_suffix :

std::string_view trim(std::string_view s) const
{
    s.remove_prefix(std::min(s.find_first_not_of(" \t\r\v\n"), s.size()));
    s.remove_suffix((s.size() - 1) - std::min(s.find_last_not_of(" \t\r\v\n"), s.size() - 1));

    return s;
}
...