Как заменить все вхождения одного символа двумя символами, используя std :: string? - PullRequest
24 голосов
/ 09 апреля 2011

Есть ли хороший простой способ заменить все вхождения "/" в std::string на "\/", чтобы избежать всех косых черт в std::string?

Ответы [ 6 ]

34 голосов
/ 10 апреля 2011

Вероятно, самый простой способ сделать это с помощью библиотеки алгоритмов ускорения строк .

  boost::replace_all(myString, "/", "\\/");

  std::string result = boost::replace_all_copy(myString, "/", "\\/");
6 голосов
/ 09 апреля 2011

Ответ - нет ... «простого» пути не существует, если вы имеете в виду однострочник, уже предоставленный стандартной библиотекой. Однако реализовать эту функцию несложно.

Прежде всего, я думаю, что, вероятно, вам также потребуется заменить \ на \\ и другие специальные символы. В этом случае использование replaceAll, данное ildjarn, будет раздражать (вам нужно будет заменить одну и ту же строку несколько раз).

По моему мнению, существует много случаев обработки строк, когда ничто не сравнится с использованием явного подхода char *. Однако в данном конкретном случае, вероятно, достаточно просто использовать индекс:

std::string escape(const std::string& s)
{
    int n = s.size(), wp = 0;
    std::vector<char> result(n*2);
    for (int i=0; i<n; i++)
    {
        if (s[i] == '/' || s[i] == '\\')
            result[wp++] = '\\';
        result[wp++] = s[i];
    }
    return std::string(&result[0], &result[wp]);
}

По сути, идея состоит в том, чтобы перемещаться по строке и добавлять дополнительный символ \ перед любым специальным символом (в приведенном выше примере я обрабатывал / и \, но вы поняли идею). Результат, как известно, имеет максимальную длину 2*n, поэтому я предварительно выделяю его, делая всю обработку O (n) (вместо этого подход replaceAll продолжает перемещать оставшуюся часть строки вправо, делая ее O (n ^) 2)). Даже для коротких строк, таких как "this is a test with /slashes/ that should be /escaped/", вышеупомянутая функция на моем ПК более эффективна (со скоростью 1,3x), даже если вызывать replaceAll только один раз и обрабатывать вместо этого два специальных символа в escape.

Обратите внимание, что эта функция естественным образом возвращает отдельную строку вместо изменения строки на месте (IMO - лучший интерфейс), и при сравнении времени мне пришлось создавать строку для каждого вызова, чтобы результаты даже сдвигались в сторону равенства из-за это добавило постоянное время.

Приведенный выше подход чтения / записи также может быть легко расширен на более сложные замены (например, замена > на &gt; или символы, не находящиеся в области печати с кодировкой %xx), сохраняя при этом хорошую эффективность для больших строк (просто один проход).

2 голосов
/ 09 апреля 2011

Пример того, как это сделать, приведен на cppreference.com std::string::replace странице :

std::string& replaceAll(std::string& context, std::string const& from, std::string const& to)
{
    std::size_t lookHere = 0;
    std::size_t foundHere;
    while((foundHere = context.find(from, lookHere)) != std::string::npos)
    {
          context.replace(foundHere, from.size(), to);
          lookHere = foundHere + to.size();
    }
    return context;
}
1 голос
/ 09 декабря 2016

Чтобы заменить все вхождения подстроки в строке другой подстрокой:

#include <iostream>

void replace_all(std::string& input, const std::string& from, const std::string& to) {
  size_t pos = 0;
  while ((pos = input.find(from, pos)) != std::string::npos) {
    input.replace(pos, from.size(), to);
    pos += to.size();
  }
}

int main() {
  std::string str("i am a geek/nerd/crazy person.");
  replace_all(str, "/", "\\/");
  std::cout << str << '\n';
}

Вывод:

$ g++-6.1.0 -std=c++17 -g -Og -Werror -Wall -Wextra -pedantic -Wold-style-cast -Wnon-virtual-dtor -Wshadow -Wcast-align -Wunused -Woverloaded-virtual -Wconversion -Wsign-conversion -Wmisleading-indentation -fsanitize=address,leak,undefined; ./a.out
i am a geek\/nerd\/crazy person.
1 голос
/ 09 апреля 2011
0 голосов
/ 10 апреля 2011

Я экстраполировал этот вопрос, чтобы реализовать потоковую реализацию, позволяющую экранировать различные символы.

Потоковая передача действительно требует больших объемов памяти [1], потому что иначе вы попадете в ад фрагментации / производительности кучи. Кроме того, это позволяет вам экранировать строки, хранящиеся практически в любом источнике, как показывают примеры

Посмотреть Live On Coliru

#include <iostream>
#include <iterator>
#include <set>
#include <sstream>
#include <string>

template <class _II, class _OI>
    static _OI escapeSomeChars(const _II inIt, const _II endIt, _OI outIt)
{
    for (_II it=inIt; it!=endIt; ++it)
        switch (*it)
        {
            case '\0': outIt++ = '\\'; outIt++ = '0'; break;
            case '\n': outIt++ = '\\'; outIt++ = 'n'; break;
            case '\\': 
            case '"' : 
            case '$' : 
            case '/' : outIt++ = '\\';
            default  : outIt++ = *it;
        }

    return outIt;
}

static std::string escapeSomeChars(const std::string& input)
{
    std::ostringstream os;
    escapeSomeChars(input.begin(), input.end(), std::ostream_iterator<char>(os));
    return os.str();
}

namespace /*anon*/ {
    struct rawchar {   // helper - see e.g. http://bytes.com/topic/c/answers/436124-copy-istream_iterator-question
        char _c; rawchar(char c=0) : _c(c) {} 
        operator const char&() const { return _c; }
        friend std::istream& operator>>(std::istream& is, rawchar& out) { return is.get(out._c); }
    };
}

int main()
{
    static const char data[] = "\"I will \\$one day \\have \\all \\\\my slash\\es escaped, much \\like\\ in the source!\n\"";

    // use the overload for std::string
    std::cout << escapeSomeChars(data);
    std::cout << std::endl;

    // streaming in & out:
    std::istringstream is(data);
    escapeSomeChars(std::istream_iterator<rawchar>(is), std::istream_iterator<rawchar>(), std::ostream_iterator<char>(std::cout));
    std::cout << std::endl;

    // but you don't need an istream, you can use any STL iterator range
    escapeSomeChars(data, data+sizeof(data)/sizeof(data[0]), std::ostream_iterator<char>(std::cout));
    std::cout << std::endl;

    // but any source and target will do:
    std::string asstring(data);
    std::set<char> chars(asstring.begin(), asstring.end());

    asstring.clear();
    escapeSomeChars(chars.begin(), chars.end(), std::back_inserter(asstring));

    std::cout << "Unique characters in data: '" << asstring << "', but properly escaped!" << std::endl;
    return 0;
}

Я выбрал переключатель, потому что он будет оптимизирован компилятором. Для динамических наборов экранируемых символов я бы предпочел какой-то поиск (вектор с std :: find подойдет, хотя для больших наборов std :: set с set :: find стал бы лучшим выбором).

Надеюсь, это поможет

[1] см., Например, Эта красивая ошибка, с которой я недавно столкнулся: GParted: Упрощенная реализация cleanup_cursor ()

...