Як мені знайти / знайти та замінити у стандартному рядку?


Відповіді:


74

Чому б не застосувати власну заміну?

void myReplace(std::string& str,
               const std::string& oldStr,
               const std::string& newStr)
{
  std::string::size_type pos = 0u;
  while((pos = str.find(oldStr, pos)) != std::string::npos){
     str.replace(pos, oldStr.length(), newStr);
     pos += newStr.length();
  }
}

3
Ви трохи возиться з пам'яттю тут із усіма викликами "замінити": складність буде n², якщо ви видалите "o" з "ooooooo ... o". Я гадаю, що можна зробити краще, але це рішення полягає в тому, що його легко зрозуміти.
Zonko

1
Чому це не фактичний цикл for, а не заплутаний цикл for?
Ширік

Я звик застосовувати принцип „найменшого сюрпризу”. Бо цикли призначені для простого збільшення індексу, більшу частину часу. Тут, на мою думку, цикл while є зрозумілішим.
yves Baumes

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

158
#include <boost/algorithm/string.hpp> // include Boost, a C++ library
...
std::string target("Would you like a foo of chocolate. Two foos of chocolate?");
boost::replace_all(target, "foo", "bar");

Ось офіційна документація на replace_all.


1
Зверніть увагу, що вам не потрібно явно створювати std :: string для шаблону та заміни: boost :: replace_all (target, "foo", "bar");
Alexis Wilke

4
+1, із застереженням: replace_allбуде segfault для версій boost> 1.43 на Sun Studio для будь-якої версії <12.3
Брайан Ванденберг

3
boostзначно збільшує час компіляції на вбудованих пристроях. Навіть чотириядерний ARMv7. 100 рядків коду компілюється за 2 хвилини, без посилення, 2 секунди.
Piotr Kula

4
@ppumkin: це означає, що ваш компілятор (або налаштування збірки, або що завгодно) відстійний, а не цільова архітектура, яка не має до цього нічого спільного.
Даніель Каміль Козар

Якщо ваш компілятор підтримує попередньо скомпільований заголовок, настійно рекомендується використовувати його під час використання boost. Це дійсно економить час.
Олексій Омельченко

33

У C ++ 11 ви можете зробити це як однолінійний з викликом regex_replace:

#include <string>
#include <regex>

using std::string;

string do_replace( string const & in, string const & from, string const & to )
{
  return std::regex_replace( in, std::regex(from), to );
}

string test = "Remove all spaces";
std::cout << do_replace(test, " ", "") << std::endl;

вихід:

Removeallspaces

Дякую, дуже простий у використанні та запам'ятовуванні!
Джуліан Деклерк

Зауважте також, що це fromможе бути регулярним виразом - так що ви можете використовувати більш складні критерії відповідності, якщо вам потрібно. Чого я не бачу, так це як це зробити, не застосовуючи якусь форму синтаксичного аналізу регулярних виразів - замість цього використовуючи лише пряму інтерпретацію fromсимволів.
Брент Бредберн,

Для цього може знадобитися сучасний компілятор. Це працювало з gcc 5.0, але у мене були проблеми з gcc 4.8.4.
Брент Бредберн,

@nobar, так, якщо я добре пам'ятаю, підтримка регулярних виразів у 4.8.x не була повною. Крім того, ви можете мати більш складні пошуки, але ви отримуєте покарання з розумом ... Це буде повільніше, ніж інші, більш прямий пошук і заміна функцій.
Alexis Wilke

2
Зверніть увагу, що це буде працювати лише для дуже основних буквено-цифрових символів і нічого іншого, не роблячи великої попередньої обробки залежно від типу рядка. Я ще не знайшов заміни рядків на основі регулярних виразів.
Піюш Соні

17

Чому б не повернути змінений рядок?

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 changed: " 
          << 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

6

Мій шаблонізований вбудований функція пошуку та заміни на місці:

template<class T>
int inline findAndReplace(T& source, const T& find, const T& replace)
{
    int num=0;
    typename T::size_t fLen = find.size();
    typename T::size_t rLen = replace.size();
    for (T::size_t pos=0; (pos=source.find(find, pos))!=T::npos; pos+=rLen)
    {
        num++;
        source.replace(pos, fLen, replace);
    }
    return num;
}

Він повертає підрахунок кількості замінених елементів (для використання, якщо ви хочете послідовно запустити це тощо). Щоб використовувати його:

std::string str = "one two three";
int n = findAndReplace(str, "one", "1");

4
Я спробував цей зразок під GCC, але він не скомпілювався - йому не сподобалось використання T :: size_t. Заміна T :: size_t на ім'я типу T :: size_type вирішує проблему.
Andrew Wyatt,

3

Найпростіший спосіб (запропонувати щось біля написаного вами) - використовувати Boost.Regex , зокрема regex_replace .

std :: string вбудував методи find () і replace (), але вони більш громіздкі в роботі, оскільки вимагають роботи з індексами та довжинами рядків.


3
Існують також алгоритми посилення рядків, включаючи replace_all (регулярний вираз може бути трохи важким для такої простої заміни).
UncleBens

3

Я вірю, що це спрацює. В якості параметра приймається const char *.

//params find and replace cannot be NULL
void FindAndReplace( std::string& source, const char* find, const char* replace )
{
   //ASSERT(find != NULL);
   //ASSERT(replace != NULL);
   size_t findLen = strlen(find);
   size_t replaceLen = strlen(replace);
   size_t pos = 0;

   //search for the next occurrence of find within source
   while ((pos = source.find(find, pos)) != std::string::npos)
   {
      //replace the found string with the replacement
      source.replace( pos, findLen, replace );

      //the next line keeps you from searching your replace string, 
      //so your could replace "hello" with "hello world" 
      //and not have it blow chunks.
      pos += replaceLen; 
   }
}

Враховуючи, що size_typeдля рядка є unsigned, ваша >=перевірка в циклі умови буде завжди true. Ви повинні std::string::nposтам користуватися.
Павло Мінаєв

size_type не підписаний. Він не підписаний на багатьох платформах, але не на всіх.
Алан

12
Чому у світі це не частина std :: string? Чи є якийсь інший серйозний клас String у світі програмування, який не пропонує операцію "знайти та замінити"? Звичайно, це частіше, ніж наявність двох ітераторів і бажання замінити текст між собою ?? Іноді std :: string здається автомобілем з лобовим склом з регульованим спектром, але жодним чином не можна скочуватися з вікна водія.
Spike0xff 02

@ Spike0xff boost hasroll_down_window
ta.speot.is

1
@gustafr: Моя помилка. Я працював над системами, де старші компілятори неправильно визначали size_t.
Алан

1
// Replace all occurrences of searchStr in str with replacer
// Each match is replaced only once to prevent an infinite loop
// The algorithm iterates once over the input and only concatenates 
// to the output, so it should be reasonably efficient
std::string replace(const std::string& str, const std::string& searchStr, 
    const std::string& replacer)
{
    // Prevent an infinite loop if the input is empty
    if (searchStr == "") {
        return str;
    }

    std::string result = "";
    size_t pos = 0;
    size_t pos2 = str.find(searchStr, pos);

    while (pos2 != std::string::npos) {
        result += str.substr(pos, pos2-pos) + replacer;
        pos = pos2 + searchStr.length();
        pos2 = str.find(searchStr, pos);
    }

    result += str.substr(pos, str.length()-pos);
    return result;
}

1
Нам потрібно лише шукати нові збіги з останнього збігу, тому алгоритм ретельно відстежує останній збіг у поз. pos2 завжди зберігає наступний збіг, тому ми об'єднуємо рядок між pos і pos2 до результату, а потім просуваємо pos і pos2. Якщо жодного іншого збігу не вдається знайти, ми об’єднуємо решту отриманого рядка.
Björn Ganster

1
#include <string>

using std::string;

void myReplace(string& str,
               const string& oldStr,
               const string& newStr) {
  if (oldStr.empty()) {
    return;
  }

  for (size_t pos = 0; (pos = str.find(oldStr, pos)) != string::npos;) {
    str.replace(pos, oldStr.length(), newStr);
    pos += newStr.length();
  }
}

Важливою є перевірка наявності порожнього oldStr. Якщо з якоїсь причини цей параметр порожній, ви застрягнете в нескінченному циклі.

Але так, використовуйте перевірений C ++ 11 або рішення Boost, якщо можете.

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