Замініть частину рядка іншою


186

Чи можливо в C ++ замінити частину рядка на іншу рядок?

В основному, я хотів би це зробити:

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

Але я хотів би використовувати бібліотеки Standard C ++.


1
можливий дублікат Що є функцією заміни рядка на C? - ой вибачте, що це C, а не C ++; Я б хотів, щоб я міг скасувати.
полігенмастильні матеріали

1
@poly Я б подумав, що це теж потрібно було просити на C ++, але я не можу його знайти
Michael Mrozek

1
У цьому питанні є std тег, але, можливо, вас можуть зацікавити рядкові алгоритми boost, які також містять широкий вибір алгоритмів заміни (inplace / copy, регистрозависимые / регістри нечутливі, замініть 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від мене за це.
sbi

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

Подвійний зворотний нахил потрібен для втечі символу втечі.


Я впевнений, що std::regex_replaceне приймає рядок Qt.
BartoszKP

1
Ти правий. Як це відбувається, QString забезпечує метод заміни, який приймає 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 substitu не займає двох рядків, як хотілося б.
Том Ліз

2
Це не працює для мене. sizeof слід замінити на рядок ("Somename"). size () - 1
TimZaman

@TimZaman: Це мене спантеличує, в документації чітко зазначено, що ви можете ініціалізувати з рядка в стилі C.
SC Madsen

5
другий аргумент повинен бути довжиною "$ 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 () - це дійсно змінює рядок inplace?
Даміан

Я коротко переглянув джерело, і схоже, що він використовує семантику переміщення, щоб перемістити передню частину старого рядка на місце, так що це не скопіюється, але новий вставлений фрагмент скопіюється у стару рядок та хвіст старого рядка копіюється в розміщений буфер старого рядка. Можливо, що рядок розширюється настільки, що весь базовий буфер перерозподіляється, але якщо ви заміните 1 на 1, як у його прикладі, я думаю, що це відбувається "на місці" або без копіювання, але якщо розширити рядок, тільки перша частина старого рядка не копіюється, і лише можливо тоді.
Мотес

6

Так, ви можете це зробити, але вам потрібно знайти позицію першого рядка з членом string ((find)), а потім замінити його заміною ().

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

Якщо ви плануєте використовувати Стандартну бібліотеку, вам справді слід придбати копію книги «Стандартна бібліотека C ++», яка дуже добре висвітлює всі ці речі.


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()повертає позицію матчу, ми не маємо проблеми з вимкненням ітераторів.


4

Це звучить як варіант

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

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


1
Як стиль, але різні рядки заплуталися ^^str.replace(str.find("%s"), string("%s").size(), "Something");
Пол Вюрц

3

Якщо всі рядки std :: string, у вас виникнуть дивні проблеми з обрізанням символів, якщо використовується, sizeof()тому що це призначено для C рядків, а не C ++. Виправлення полягає у використанні .size()методу класу std::string.

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

Це замінює вбудований sHaystack inline - не потрібно виконувати завдання = на цьому.

Приклад використання:

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;

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

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

}

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(), "\\", "/"), ":", "");

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

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);
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.