Заменить часть строки другой строкой

186

Возможно ли в C ++ заменить часть строки другой строкой?

В принципе, я хотел бы сделать это:

QString string("hello $name");
string.replace("$name", "Somename");

Но я бы хотел использовать стандартные библиотеки C ++.

Том Лиз
источник
1
Возможный дубликат Какой функции заменить строку в C? - ой, извините, что это C, а не C ++; Я хотел бы отменить голосование.
полигенасмазочные материалы
1
@ Поли Я бы подумал, что об этом, наверное, тоже просили С ++, но я не могу его найти
Майкл Мрозек
1
В этом вопросе есть тэг std, но, возможно, вас могут заинтересовать строковые алгоритмы boost, которые также включают в себя широкий выбор алгоритмов замены (inplace / copy, чувствительно к регистру / нечувствительно к регистру, replace first / last / all / n-th ).
UncleBens
@Michael Mrozek На сайте stackoverflow.com/questions/3418231/ есть еще один, но он более новый, и ваш метод replaceAll здесь более надежный.
Дэйв-Холм

Ответы:

288

Есть функция для поиска подстроки в строке ( find) и функция для замены определенного диапазона в строке другой строкой ( replace), так что вы можете комбинировать их, чтобы получить желаемый эффект:

bool replace(std::string& str, const std::string& from, const std::string& to) {
    size_t start_pos = str.find(from);
    if(start_pos == std::string::npos)
        return false;
    str.replace(start_pos, from.length(), to);
    return true;
}

std::string string("hello $name");
replace(string, "$name", "Somename");

В ответ на комментарий, я думаю, replaceAllбудет выглядеть примерно так:

void replaceAll(std::string& str, const std::string& from, const std::string& to) {
    if(from.empty())
        return;
    size_t start_pos = 0;
    while((start_pos = str.find(from, start_pos)) != std::string::npos) {
        str.replace(start_pos, from.length(), to);
        start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
    }
}
Майкл Мрозек
источник
2
Как бы это исправить, если бы в исходной строке было более одного экземпляра «$ name», и я хотел заменить их все.
Том Лиз
1
Почему нет fromи toпередается по constссылке? Что делает твоя функция, если fromее там нет? -1от меня за это.
2010 г.,
10
@sbi Исправлено, хотя вы могли бы сформулировать это как рекомендации, а не атаки - мне это просто не пришло в голову, я редко думаю об этом, constи если бы я написал такой служебный метод, я бы назвал его, только если бы знал замена действительна
Майкл Мрозек
10
@Michael: Хорошо, я превратил свой голос против в голосование против. Увольнение constигнорирует один из лучших инструментов C ++. Передача по constссылке должна быть режимом по умолчанию для параметров функции. (FTR, без const, вы не могли бы даже передавать строковые литералы в вашу функцию, потому что вы не можете привязать временные значения к не constссылкам. Поэтому функция даже не будет делать то, для чего она была написана.)
sbi
19
Это все еще единственное решение в 2018 году? Если так и любой комитет C ++ читает это, разберитесь. Это смущает. разделите (строка, строка) и замените (строка, строка), пожалуйста!
user997112
96

С C ++ 11 вы можете использовать std::regexтак:

#include <regex>
...
std::string string("hello $name");
string = std::regex_replace(string, std::regex("\\$name"), "Somename");

Двойная обратная косая черта необходима для выхода из escape-символа.

Том
источник
Я уверен, std::regex_replaceчто не принимает строку Qt.
BartoszKP
1
Ты прав. Когда это происходит, QString предоставляет метод replace, который принимает QRexExp, позволяя использовать собственный материал Qt. Но я думаю, что текущий ответ можно исправить, заменив stringна string.toStdString().
Том
1
Или просто изменив Stringна std::string, потому что вопрос не связан с Qt. Пожалуйста, подумайте об этом - я с удовольствием опишу ваш ответ позже.
BartoszKP
5
Необработанная строка позволяет писать R"(\$name)"вместо "\\$name".
Jarod42
2
Насколько это будет медленнее, чем поиск / замена без учета времени создания std :: regex?
jw_
18

std::stringесть replaceметод, это то, что вы ищете?

Вы можете попробовать:

s.replace(s.find("$name"), sizeof("$name") - 1, "Somename");

Я не пробовал себя, просто прочитал документацию find()и replace().

СК Мадсен
источник
2
Из того, что я вижу, метод замены std :: string не берет две строки, как хотелось бы.
Том Лиз
2
Это не работает для меня. sizeof следует заменить на строку («Somename»). size () - 1
TimZaman
@TimZaman: Меня это озадачивает, в документации четко указано, что вы можете инициализировать из строки в стиле C.
SC Madsen
5
2-ой аргумент должен быть длиной «$ name» (вместо длины «Somename»), не так ли?
Даниэль Кисс
10

Чтобы получить новую строку, используйте это:

std::string ReplaceString(std::string subject, const std::string& search,
                          const std::string& replace) {
    size_t pos = 0;
    while ((pos = subject.find(search, pos)) != std::string::npos) {
         subject.replace(pos, search.length(), replace);
         pos += replace.length();
    }
    return subject;
}

Если вам нужна производительность, вот оптимизированная функция, которая модифицирует входную строку, она не создает копию строки:

void ReplaceStringInPlace(std::string& subject, const std::string& search,
                          const std::string& replace) {
    size_t pos = 0;
    while ((pos = subject.find(search, pos)) != std::string::npos) {
         subject.replace(pos, search.length(), replace);
         pos += replace.length();
    }
}

тесты:

std::string input = "abc abc def";
std::cout << "Input string: " << input << std::endl;

std::cout << "ReplaceString() return value: " 
          << ReplaceString(input, "bc", "!!") << std::endl;
std::cout << "ReplaceString() input string not modified: " 
          << input << std::endl;

ReplaceStringInPlace(input, "bc", "??");
std::cout << "ReplaceStringInPlace() input string modified: " 
          << input << std::endl;

Вывод:

Input string: abc abc def
ReplaceString() return value: a!! a!! def
ReplaceString() input string not modified: abc abc def
ReplaceStringInPlace() input string modified: a?? a?? def
Чарек Томчак
источник
Ваш вызов subject.replace в ReplaceStringInPlace () действительно изменяет строку на месте?
Дамиан
Я кратко посмотрел на источник, и похоже, что он использует семантику перемещения, чтобы переместить переднюю часть старой строки на место, чтобы она не копировалась, но новая вставленная часть копируется в старую строку и хвост старой строки копируется в буфер с измененным размером старой строки. Возможно, строка расширяется настолько, что весь базовый буфер перераспределяется, но если вы замените 1 на 1, как в его примере, я думаю, что это произойдет «на месте» или без какого-либо копирования, но если вы расширите строку, только первая часть старой строки не копируется, и только возможно тогда.
Motes
6

Да, вы можете сделать это, но вы должны найти позицию первой строки с помощью элемента find () строки, а затем заменить его элементом replace ().

string s("hello $name");
size_type pos = s.find( "$name" );
if ( pos != string::npos ) {
   s.replace( pos, 5, "somename" );   // 5 = length( $name )
}

Если вы планируете использовать Стандартную библиотеку, вам действительно нужно получить экземпляр книги «Стандартная библиотека С ++», которая очень хорошо охватывает все эти вещи.

Дрю Ноакс
источник
1
Это size_t и не size_type
Revo
1
Это std :: string :: size_type, а не size_t или неукрашенный size_type.
jmucchiello
5

Я обычно использую это:

std::string& replace(std::string& s, const std::string& from, const std::string& to)
{
    if(!from.empty())
        for(size_t pos = 0; (pos = s.find(from, pos)) != std::string::npos; pos += to.size())
            s.replace(pos, from.size(), to);
    return s;
}

Он многократно вызывает, std::string::find()чтобы найти другие вхождения искомой строки, пока std::string::find()ничего не находит. Поскольку std::string::find()возвращает позицию совпадения, у нас нет проблемы аннулирования итераторов.

Galik
источник
4

Это звучит как вариант

string.replace(string.find("%s"), string("%s").size(), "Something");

Вы могли бы обернуть это в функцию, но это решение в одну строку звучит приемлемо. Проблема в том, что это изменит только первый случай, вы можете зацикливаться на нем, но это также позволяет вам вставить несколько переменных в эту строку с одним и тем же token ( %s)

maxoumime
источник
1
Понравился стиль, но обнаружил, что разные строки сбивают с толку ^^str.replace(str.find("%s"), string("%s").size(), "Something");
Paul Würtz
3

Если все строки являются std :: string, вы обнаружите странные проблемы с обрезкой символов при использовании, sizeof()потому что она предназначена для строк C, а не строк C ++. Исправление заключается в использовании .size()метода класса std::string.

sHaystack.replace(sHaystack.find(sNeedle), sNeedle.size(), sReplace);

Это заменяет встроенный sHaystack - нет необходимости выполнять задание =.

Пример использования:

std::string sHaystack = "This is %XXX% test.";
std::string sNeedle = "%XXX%";
std::string sReplace = "my special";
sHaystack.replace(sHaystack.find(sNeedle),sNeedle.size(),sReplace);
std::cout << sHaystack << std::endl;
Volomike
источник
2
wstring myString = L"Hello $$ this is an example. By $$.";
wstring search = L"$$";
wstring replace = L"Tom";
for (int i = myString.find(search); i >= 0; i = myString.find(search))
    myString.replace(i, search.size(), replace);
user3016543
источник
2

Если вы хотите сделать это быстро, вы можете использовать два подхода сканирования. Псевдокод:

  1. первый разбор найти, сколько совпадающих символов.
  2. увеличить длину строки.
  3. второй разбор Начните с конца строки, когда мы получим совпадение, которое мы заменим, иначе мы просто скопируем символы из первой строки.

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

И пример кода C ++ 11, но я ищу только один символ.

#include <string>
#include <iostream>
#include <algorithm>
using namespace std;

void ReplaceString(string& subject, char search, const string& replace)
{   
    size_t initSize = subject.size();
    int count = 0;
    for (auto c : subject) { 
        if (c == search) ++count;
    }

    size_t idx = subject.size()-1 + count * replace.size()-1;
    subject.resize(idx + 1, '\0');

    string reverseReplace{ replace };
    reverse(reverseReplace.begin(), reverseReplace.end());  

    char *end_ptr = &subject[initSize - 1];
    while (end_ptr >= &subject[0])
    {
        if (*end_ptr == search) {
            for (auto c : reverseReplace) {
                subject[idx - 1] = c;
                --idx;              
            }           
        }
        else {
            subject[idx - 1] = *end_ptr;
            --idx;
        }
        --end_ptr;
    }
}

int main()
{
    string s{ "Mr John Smith" };
    ReplaceString(s, ' ', "%20");
    cout << s << "\n";

}
Damian
источник
1
std::string replace(std::string base, const std::string from, const std::string to) {
    std::string SecureCopy = base;

    for (size_t start_pos = SecureCopy.find(from); start_pos != std::string::npos; start_pos = SecureCopy.find(from,start_pos))
    {
        SecureCopy.replace(start_pos, from.length(), to);
    }

    return SecureCopy;
}
Лукас Чивали
источник
2
Можете ли вы объяснить этот код (в вашем ответе)? Вы можете получить больше голосов таким образом!
Парень со шляпой
1

Моя собственная реализация, учитывая, что размер строки нужно изменить только один раз, тогда может произойти замена.

template <typename T>
std::basic_string<T> replaceAll(const std::basic_string<T>& s, const T* from, const T* to)
{
    auto length = std::char_traits<T>::length;
    size_t toLen = length(to), fromLen = length(from), delta = toLen - fromLen;
    bool pass = false;
    std::string ns = s;

    size_t newLen = ns.length();

    for (bool estimate : { true, false })
    {
        size_t pos = 0;

        for (; (pos = ns.find(from, pos)) != std::string::npos; pos++)
        {
            if (estimate)
            {
                newLen += delta;
                pos += fromLen;
            }
            else
            {
                ns.replace(pos, fromLen, to);
                pos += delta;
            }
        }

        if (estimate)
            ns.resize(newLen);
    }

    return ns;
}

Использование может быть, например, так:

std::string dirSuite = replaceAll(replaceAll(relPath.parent_path().u8string(), "\\", "/"), ":", "");
TarmoPikaro
источник
0

Я только сейчас изучаю C ++, но редактируя часть ранее опубликованного кода, я бы, вероятно, использовал что-то вроде этого. Это дает вам возможность заменить 1 или несколько экземпляров, а также позволяет указать начальную точку.

using namespace std;

// returns number of replacements made in string
long strReplace(string& str, const string& from, const string& to, size_t start = 0, long count = -1) {
    if (from.empty()) return 0;

    size_t startpos = str.find(from, start);
    long replaceCount = 0;

    while (startpos != string::npos){
        str.replace(startpos, from.length(), to);
        startpos += to.length();
        replaceCount++;

        if (count > 0 && replaceCount >= count) break;
        startpos = str.find(from, startpos);
    }

    return replaceCount;
}
someprogrammer
источник
0

Это может быть даже лучше использовать

void replace(string& input, const string& from, const string& to)
{
    while(true)
    {
        size_t startPosition = input.find(from);
        if(startPosition == string::npos)
            break;
        input.replace(startPosition, from.length(), to);
    }
}
Яшвант Кумар
источник