Remplacer une partie d'une chaîne par une autre chaîne

192

Est-il possible en C ++ de remplacer une partie d'une chaîne par une autre chaîne?

En gros, je voudrais faire ceci:

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

Mais j'aimerais utiliser les bibliothèques Standard C ++.

Tom Leese
la source
1
duplication possible de Quelle est la fonction pour remplacer la chaîne en C? - Oups désolé que ce soit C, pas C ++; J'aimerais pouvoir annuler le vote.
polygenelubricants
1
@poly Je pense que cela a dû être demandé pour C ++ aussi, mais je ne le trouve pas
Michael Mrozek
1
Il y a une balise std sur la question, mais peut-être que vous pourriez être intéressé par les algorithmes de chaîne de boost, qui incluent également un large choix d'algorithmes de remplacement (inplace / copy, case sensitive / case insensitive, replace first / last / all / n-th ).
UncleBens
@Michael Mrozek Il y en a un sur stackoverflow.com/questions/3418231/ ... mais il est plus récent et votre méthode replaceAll ici est plus robuste.
dave-holm

Réponses:

299

Il existe une fonction pour trouver une sous-chaîne dans une chaîne ( find), et une fonction pour remplacer une plage particulière dans une chaîne par une autre chaîne ( replace), vous pouvez donc les combiner pour obtenir l'effet souhaité:

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

En réponse à un commentaire, je pense que replaceAllcela ressemblerait probablement à ceci:

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'
    }
}
Michael Mrozek
la source
2
Comment le réparerais-je si la chaîne d'origine avait plus d'une instance de "$ name" et que je voulais toutes les remplacer.
Tom Leese
2
Pourquoi ne le sont pas fromet sont -ils topassés par constréférence? Quelle est votre fonction si ce fromn'est pas là? -1de moi pour ça.
sbi
10
@sbi Fixe, même si vous auriez pu le formuler sous forme de recommandations au lieu d'attaques - cela ne m'est tout simplement pas venu à l'esprit, je pense rarement à l'utiliser constet si j'écrivais une méthode utilitaire comme celle-ci, je ne l'appellerais que si je connaissais le remplacement étaient valides
Michael Mrozek
11
@Michael: Bien, j'ai transformé mon vote défavorable en vote favorable. Le rejet constrevient à ne pas tenir compte de l'un des meilleurs outils de C ++. Le passage par constréférence doit être le mode par défaut pour les paramètres de fonction. (FTR, sans le const, vous ne pourriez même pas passer des chaînes littérales à votre fonction, car vous ne pouvez pas lier des temporaires à des non- constréférences. Ainsi, la fonction ne ferait même pas ce sur quoi elle a été écrite.)
sbi
26
Est-ce toujours la seule solution en 2018? Si tel est le cas et qu'un comité C ++ lit ceci, triez-le. C'est gênant. diviser (chaîne, chaîne) et remplacer (chaîne, chaîne) s'il vous plaît!
user997112
104

Avec C ++ 11, vous pouvez utiliser std::regexcomme ceci:

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

La double barre oblique inverse est requise pour échapper un caractère d'échappement.

À M
la source
Je suis presque sûr qu'il std::regex_replacen'accepte pas la chaîne de Qt.
BartoszKP
1
Tu as raison. En l'occurrence, QString fournit une méthode de remplacement qui accepte un QRexExp, permettant d'utiliser les propres trucs de Qt. Mais je pense que la réponse actuelle pourrait être corrigée en remplaçant stringpar string.toStdString().
Tom
1
Ou simplement en changeant Stringen std::string, car la question n'est pas liée à Qt. Veuillez envisager de le faire - je serai ravi de voter par la suite.
BartoszKP
6
La chaîne brute permet d'écrire à la R"(\$name)"place de "\\$name".
Jarod42
5
Dans quelle mesure cela sera-t-il plus lent que find / replace sans tenir compte du temps de construction de std :: regex?
jw_
20

std::stringa une replaceméthode, est-ce ce que vous recherchez?

Tu pourrais essayer:

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

Je n'ai pas essayé moi-même, lisez simplement la documentation sur find()et replace().

SC Madsen
la source
2
D'après ce que je peux voir, la méthode de remplacement std :: string ne prend pas deux chaînes comme je le voudrais.
Tom Leese
2
Cela ne fonctionne pas pour moi. sizeof doit être remplacé par string ("Somename"). size () - 1
TimZaman
@TimZaman: Cela me laisse perplexe, la documentation indique clairement que vous pouvez initialiser à partir d'une chaîne de style C.
SC Madsen
5
le 2ème argument devrait être la longueur de "$ name" (au lieu de la longueur de "Somename"), n'est-ce pas?
Daniel Kiss
10

Pour que la nouvelle chaîne soit renvoyée, utilisez ceci:

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

Si vous avez besoin de performances, voici une fonction optimisée qui modifie la chaîne d'entrée, elle ne crée pas de copie de la chaîne:

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

Tests:

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;

Production:

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
Czarek Tomczak
la source
Votre appel à subject.replace dans ReplaceStringInPlace () modifie-t-il vraiment la chaîne en place?
Damian
J'ai brièvement regardé la source et il semble qu'il utilise la sémantique de déplacement pour déplacer l'avant de l'ancienne chaîne en place, de sorte que ce ne soit pas copié, mais le nouveau morceau inséré est copié dans l'ancienne chaîne et la queue de l'ancienne chaîne est copié dans la mémoire tampon redimensionnée de l'ancienne chaîne. Il est possible que la chaîne se développe tellement que tout le tampon sous-jacent soit réalloué, mais si vous remplacez 1 à 1 comme dans son exemple, je pense que cela se produit "en place", ou sans aucune copie, mais si vous développez la chaîne, seule la première partie de l'ancienne chaîne n'est pas copiée, et peut-être alors seulement.
Motes
6

Oui, vous pouvez le faire, mais vous devez trouver la position de la première chaîne avec le membre find () de la chaîne, puis la remplacer par son membre replace ().

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

Si vous prévoyez d'utiliser la bibliothèque standard, vous devriez vraiment vous procurer un exemplaire du livre The C ++ Standard Library qui couvre très bien tout cela.

Drew Noakes
la source
1
c'est size_t et non size_type
revo
1
Il s'agit de std :: string :: size_type, pas de size_t ou du size_type sans ornement.
jmucchiello
5

Cela ressemble à une option

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

Vous pouvez intégrer cela dans une fonction, mais cette solution en une ligne semble acceptable. Le problème est que cela ne changera que la première occurrence, vous voudrez peut-être faire une boucle dessus, mais cela vous permet également d'insérer plusieurs variables dans cette chaîne avec le même jeton ( %s)

maxoumime
la source
1
J'aime le style mais j'ai trouvé les différentes cordes déroutantes ^^str.replace(str.find("%s"), string("%s").size(), "Something");
Paul Würtz
5

J'utilise généralement ceci:

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

Il appelle std::string::find()à plusieurs reprises pour localiser d'autres occurrences de la chaîne recherchée jusqu'à ce std::string::find()qu'il ne trouve rien. Parce que std::string::find()renvoie la position de la correspondance, nous n'avons pas le problème d'invalider les itérateurs.

Galik
la source
3

Si toutes les chaînes sont std :: string, vous rencontrerez des problèmes étranges avec la coupure des caractères si vous l'utilisez sizeof()car elle est destinée aux chaînes C, pas aux chaînes C ++. Le correctif consiste à utiliser la .size()méthode de classe de std::string.

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

Cela remplace sHaystack en ligne - pas besoin de faire une affectation = à ce sujet.

Exemple d'utilisation:

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
la source
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
la source
2

Si vous voulez le faire rapidement, vous pouvez utiliser une approche à deux analyses. Pseudo code:

  1. première analyse. trouver le nombre de caractères correspondants.
  2. étendre la longueur de la chaîne.
  3. deuxième analyse. Commencez par la fin de la chaîne lorsque nous obtenons une correspondance que nous remplaçons, sinon nous copions simplement les caractères de la première chaîne.

Je ne suis pas sûr que cela puisse être optimisé pour un algorithme en place.

Et un exemple de code C ++ 11 mais je ne recherche qu'un seul caractère.

#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
la source
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;
}
Lucas Civali
la source
2
Pouvez-vous expliquer ce code (dans votre réponse)? Vous pourriez obtenir plus de votes positifs de cette façon!
Le gars au chapeau
1

Ma propre implémentation, en tenant compte du fait que la chaîne ne doit être redimensionnée qu'une seule fois, puis le remplacement peut se produire.

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

L'utilisation pourrait être par exemple comme ceci:

std::string dirSuite = replaceAll(replaceAll(relPath.parent_path().u8string(), "\\", "/"), ":", "");
TarmoPikaro
la source
1

Cela pourrait être encore mieux à utiliser

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);
    }
}
Yashwanth Kumar
la source
string s = "ha"; replace(s, "h", "uhoh");
Jonathan Wakely
0

J'apprends tout juste le C ++, mais en éditant une partie du code précédemment publié, j'utiliserais probablement quelque chose comme ça. Cela vous donne la possibilité de remplacer une ou plusieurs instances et vous permet également de spécifier le point de départ.

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;
}
un programmeur
la source
0

Vous pouvez utiliser ce code pour supprimer la sous-chaîne et également remplacer, et également supprimer les espaces blancs supplémentaires. code:

#include<bits/stdc++.h>
using namespace std ;
void removeSpaces(string &str)
{
   
    int n = str.length();

    int i = 0, j = -1;

    bool spaceFound = false;

    while (++j <= n && str[j] == ' ');

    while (j <= n)
    {
        if (str[j] != ' ')
        {
          
            if ((str[j] == '.' || str[j] == ',' ||
                 str[j] == '?') && i - 1 >= 0 &&
                 str[i - 1] == ' ')
                str[i - 1] = str[j++];

            else
                
                str[i++] = str[j++];

            
            spaceFound = false;
        }
        else if (str[j++] == ' ')
        {
            
            if (!spaceFound)
            {
                str[i++] = ' ';
                spaceFound = true;
            }
        }
    }

    if (i <= 1)
        str.erase(str.begin() + i, str.end());
    else
        str.erase(str.begin() + i - 1, str.end());
}
int main()
{
    string s;
    cin>>s;
    for(int i=s.find("WUB");i>=0;i=s.find("WUB"))
    {
        s.replace(i,3," ");
    }
    removeSpaces(s);
    cout<<s<<endl;

    return 0;
}
mubasshir00
la source