Comment concaténer plusieurs chaînes C ++ sur une seule ligne?


150

C # a une fonction de syntaxe où vous pouvez concaténer de nombreux types de données sur une seule ligne.

string s = new String();
s += "Hello world, " + myInt + niceToSeeYouString;
s += someChar1 + interestingDecimal + someChar2;

Quel serait l'équivalent en C ++? Pour autant que je sache, vous devrez tout faire sur des lignes séparées car il ne prend pas en charge plusieurs chaînes / variables avec l'opérateur +. C'est correct, mais cela n'a pas l'air aussi soigné.

string s;
s += "Hello world, " + "nice to see you, " + "or not.";

Le code ci-dessus produit une erreur.


4
Comme expliqué ailleurs, ce n'est pas parce que "il ne prend pas en charge plusieurs chaînes / variables avec l'opérateur +" - mais plutôt parce que vous essayez d'ajouter des char *pointeurs les uns aux autres. C'est ce qui génère l'erreur - car la somme des pointeurs n'a pas de sens. Comme indiqué ci-dessous, transformez au moins le 1er opérande en an std::string, et il n'y a aucune erreur.
underscore_d

Quelle erreur s'est produite?
Wolf

Réponses:


240
#include <sstream>
#include <string>

std::stringstream ss;
ss << "Hello, world, " << myInt << niceToSeeYouString;
std::string s = ss.str();

Jetez un œil à cet article du gourou de la semaine de Herb Sutter: Les formateurs de cordes de Manor Farm


6
Essayez ceci:std::string s = static_cast<std::ostringstream&>(std::ostringstream().seekp(0) << "HelloWorld" << myInt << niceToSeeYouString).str();
Byzantian

42
ss << "Wow, la concaténation de chaînes en C ++ est impressionnante" << "ou pas."
joaerl

4
Pour nommer une autre manière: en utilisant plusieurs append: string s = string ("abc"). Append ("def"). Append (otherStrVar) .append (to_string (123));
Patricio Rossi

1
std::stringstream ss; ss << "Hello, world, " << myInt << niceToSeeYouString; std::string s = ss.str();est à peu près une ligne
Kotauskas

74

En 5 ans, personne ne l'a mentionné .append?

#include <string>

std::string s;
s.append("Hello world, ");
s.append("nice to see you, ");
s.append("or not.");

Parce que c'est encombrant par rapport à l'ajout d'un texte sur une seule ligne.
Hi-Angel

11
s.append("One"); s.append(" line");
Jonny

16
@Jonny s.append("One").append(" expression");Peut-être devrais -je modifier l'original pour utiliser la valeur de retour de cette façon?
Eponyme

5
@ SilverMöls L'OP déclare ssur une ligne différente dans le code C # équivalent et dans son code C ++ non compilant. Son C ++ désiré est s += "Hello world, " + "nice to see you, " + "or not.";qui peut être écrits.append("Hello world, ").append("nice to see you, ").append("or not.");
Éponyme

4
Un avantage majeur de appendest qu'il fonctionne également lorsque les chaînes contiennent des caractères NUL.
John S.

62
s += "Hello world, " + "nice to see you, " + "or not.";

Ces littéraux de tableau de caractères ne sont pas des chaînes C ++ std :: strings - vous devez les convertir:

s += string("Hello world, ") + string("nice to see you, ") + string("or not.");

Pour convertir des ints (ou tout autre type streamable), vous pouvez utiliser un boost lexical_cast ou fournir votre propre fonction:

template <typename T>
string Str( const T & t ) {
   ostringstream os;
   os << t;
   return os.str();
}

Vous pouvez maintenant dire des choses comme:

string s = string("The meaning is ") + Str( 42 );

16
Il vous suffit de convertir explicitement le premier: s + = string ("Hello world,") + "Content to see you," + "or not.";
Ferruccio

8
Oui, mais je ne pouvais pas supporter d'expliquer pourquoi!

1
boost :: lexical_cast - agréable et similaire sur votre fonction Str :)
bayda

2
Les concaténations effectuées à droite du constructeur string("Hello world")sont effectuées via operator+()défini dans la classe string. S'il n'y a pas d' stringobjet dans l'expression, la concaténation devient une simple somme de pointeurs char char*.
davide

41

Votre code peut être écrit sous la forme 1 ,

s = "Hello world," "nice to see you," "or not."

... mais je doute que ce soit ce que vous recherchez. Dans votre cas, vous recherchez probablement des flux:

std::stringstream ss;
ss << "Hello world, " << 42 << "nice to see you.";
std::string s = ss.str();

1 " peut être écrit comme ": cela ne fonctionne que pour les chaînes littérales. La concaténation est effectuée par le compilateur.


11
Votre premier exemple mérite d'être mentionné, mais veuillez également mentionner qu'il ne fonctionne que pour "concaténer" des chaînes littérales (le compilateur effectue lui-même la concaténation).
j_random_hacker

Le premier exemple a déclenché une erreur pour moi si une chaîne était précédemment déclarée comme par exemple const char smthg[] = "smthg": / Est-ce un bogue?
Salut-Angel

@ Salut-Angel Malheureusement non, à la place, vous pouvez #definevotre chaîne pour contourner cela, bien que cela pose ses propres problèmes.
cz

27

L'utilisation de littéraux définis par l'utilisateur C ++ 14 et std::to_stringle code devient plus facile.

using namespace std::literals::string_literals;
std::string str;
str += "Hello World, "s + "nice to see you, "s + "or not"s;
str += "Hello World, "s + std::to_string(my_int) + other_string;

Notez que la concaténation de littéraux de chaîne peut être effectuée au moment de la compilation. Retirez simplement le fichier +.

str += "Hello World, " "nice to see you, " "or not";

2
Depuis C ++ 11, vous pouvez utiliser std :: to_string
Patricio Rossi

les littéraux définis par l'utilisateur également depuis C ++ 11 <> . J'ai édité.
Stack Danny

@StackDanny Le changement est faux. Quand je dis "C ++ 14", je me réfère au std::literals::string_literalsconcept, pas au concept UDL.
Rapptz

16

Pour offrir une solution plus globale: Une fonction concatpeut être implémentée pour réduire la solution «classique» basée sur un flux de chaînes à une seule instruction . Il est basé sur des modèles variadiques et une transmission parfaite.


Usage:

std::string s = concat(someObject, " Hello, ", 42, " I concatenate", anyStreamableType);

La mise en oeuvre:

void addToStream(std::ostringstream&)
{
}

template<typename T, typename... Args>
void addToStream(std::ostringstream& a_stream, T&& a_value, Args&&... a_args)
{
    a_stream << std::forward<T>(a_value);
    addToStream(a_stream, std::forward<Args>(a_args)...);
}

template<typename... Args>
std::string concat(Args&&... a_args)
{
    std::ostringstream s;
    addToStream(s, std::forward<Args>(a_args)...);
    return s.str();
}

cela ne deviendrait-il pas un gonflement au moment de la compilation s'il y avait plusieurs combinaisons différentes dans une grande base de code.
Shital Shah

1
@ShitalShah pas plus que simplement écrire manuellement ce truc en ligne, puisque ces fonctions d'assistance seront de toute façon simplement intégrées.
underscore_d

13

En C ++ 20, vous pourrez faire:

auto s = std::format("{}{}{}", "Hello world, ", myInt, niceToSeeYouString);

Jusque-là, vous pouvez faire de même avec la bibliothèque {fmt} :

auto s = fmt::format("{}{}{}", "Hello world, ", myInt, niceToSeeYouString);

Avertissement : je suis l'auteur de {fmt}.


7

boost :: format

ou std :: stringstream

std::stringstream msg;
msg << "Hello world, " << myInt  << niceToSeeYouString;
msg.str(); // returns std::string object

6

Le problème réel était que la concaténation de littéraux de chaîne avec +échoue en C ++:

string s;
s += "Hello world, " + "nice to see you, " + "or not.";
Le code ci-dessus produit une erreur.

En C ++ (également en C), vous concaténez des chaînes littérales en les plaçant simplement les unes à côté des autres:

string s0 = "Hello world, " "nice to see you, " "or not.";
string s1 = "Hello world, " /*same*/ "nice to see you, " /*result*/ "or not.";
string s2 = 
    "Hello world, " /*line breaks in source code as well as*/ 
    "nice to see you, " /*comments don't matter*/ 
    "or not.";

Cela a du sens, si vous générez du code dans des macros:

#define TRACE(arg) cout << #arg ":" << (arg) << endl;

... une macro simple qui peut être utilisée comme ça

int a = 5;
TRACE(a)
a += 7;
TRACE(a)
TRACE(a+7)
TRACE(17*11)

( démo en direct ... )

ou, si vous insistez pour utiliser les +littéraux de chaîne for (comme déjà suggéré par underscore_d ):

string s = string("Hello world, ")+"nice to see you, "+"or not.";

Une autre solution combine une chaîne et un const char*pour chaque étape de concaténation

string s;
s += "Hello world, "
s += "nice to see you, "
s += "or not.";

J'utilise aussi beaucoup cette technique, mais que faire si une ou plusieurs variables sont int / string? .eg string s = "abc" "def" (int) y "ghi" (std :: string) z "1234"; alors, sprintf est toujours la meilleure des pires solutions.
Bart Mensfort

@BartMensfort sprintfest bien sûr une option, mais il existe également std :: stringstream qui évite les problèmes de tampons sous-dimensionnés.
Wolf


3

Vous auriez à définir operator + () pour chaque type de données que vous voudriez concéder à la chaîne, mais comme l'opérateur << est défini pour la plupart des types, vous devez utiliser std :: stringstream.

Merde, battu de 50 secondes ...


1
Vous ne pouvez pas réellement définir de nouveaux opérateurs sur des types intégrés tels que char et int.
Tyler McHenry

1
@TylerMcHenry Non pas que je le recommande dans ce cas, mais vous pouvez certainement:std::string operator+(std::string s, int i){ return s+std::to_string(i); }
Éponyme

3

Si vous écrivez le +=, il ressemble presque à C #

string s("Some initial data. "); int i = 5;
s = s + "Hello world, " + "nice to see you, " + to_string(i) + "\n";

3

Comme d'autres l'ont dit, le principal problème avec le code OP est que l'opérateur +ne concatène pas const char *; cela fonctionne avec std::string, cependant.

Voici une autre solution qui utilise des lambdas C ++ 11 et for_eachet permet de fournir un separatorpour séparer les chaînes:

#include <vector>
#include <algorithm>
#include <iterator>
#include <sstream>

string join(const string& separator,
            const vector<string>& strings)
{
    if (strings.empty())
        return "";

    if (strings.size() == 1)
        return strings[0];

    stringstream ss;
    ss << strings[0];

    auto aggregate = [&ss, &separator](const string& s) { ss << separator << s; };
    for_each(begin(strings) + 1, end(strings), aggregate);

    return ss.str();
}

Usage:

std::vector<std::string> strings { "a", "b", "c" };
std::string joinedStrings = join(", ", strings);

Il semble bien évoluer (linéairement), au moins après un test rapide sur mon ordinateur; voici un test rapide que j'ai écrit:

#include <vector>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <sstream>
#include <chrono>

using namespace std;

string join(const string& separator,
            const vector<string>& strings)
{
    if (strings.empty())
        return "";

    if (strings.size() == 1)
        return strings[0];

    stringstream ss;
    ss << strings[0];

    auto aggregate = [&ss, &separator](const string& s) { ss << separator << s; };
    for_each(begin(strings) + 1, end(strings), aggregate);

    return ss.str();
}

int main()
{
    const int reps = 1000;
    const string sep = ", ";
    auto generator = [](){return "abcde";};

    vector<string> strings10(10);
    generate(begin(strings10), end(strings10), generator);

    vector<string> strings100(100);
    generate(begin(strings100), end(strings100), generator);

    vector<string> strings1000(1000);
    generate(begin(strings1000), end(strings1000), generator);

    vector<string> strings10000(10000);
    generate(begin(strings10000), end(strings10000), generator);

    auto t1 = chrono::system_clock::now();
    for(int i = 0; i<reps; ++i)
    {
        join(sep, strings10);
    }

    auto t2 = chrono::system_clock::now();
    for(int i = 0; i<reps; ++i)
    {
        join(sep, strings100);
    }

    auto t3 = chrono::system_clock::now();
    for(int i = 0; i<reps; ++i)
    {
        join(sep, strings1000);
    }

    auto t4 = chrono::system_clock::now();
    for(int i = 0; i<reps; ++i)
    {
        join(sep, strings10000);
    }

    auto t5 = chrono::system_clock::now();

    auto d1 = chrono::duration_cast<chrono::milliseconds>(t2 - t1);
    auto d2 = chrono::duration_cast<chrono::milliseconds>(t3 - t2);
    auto d3 = chrono::duration_cast<chrono::milliseconds>(t4 - t3);
    auto d4 = chrono::duration_cast<chrono::milliseconds>(t5 - t4);

    cout << "join(10)   : " << d1.count() << endl;
    cout << "join(100)  : " << d2.count() << endl;
    cout << "join(1000) : " << d3.count() << endl;
    cout << "join(10000): " << d4.count() << endl;
}

Résultats (millisecondes):

join(10)   : 2
join(100)  : 10
join(1000) : 91
join(10000): 898

3

Peut-être aimeriez-vous que ma solution "Streamer" le fasse vraiment en une seule ligne:

#include <iostream>
#include <sstream>
using namespace std;

class Streamer // class for one line string generation
{
public:

    Streamer& clear() // clear content
    {
        ss.str(""); // set to empty string
        ss.clear(); // clear error flags
        return *this;
    }

    template <typename T>
    friend Streamer& operator<<(Streamer& streamer,T str); // add to streamer

    string str() // get current string
    { return ss.str();}

private:
    stringstream ss;
};

template <typename T>
Streamer& operator<<(Streamer& streamer,T str)
{ streamer.ss<<str;return streamer;}

Streamer streamer; // make this a global variable


class MyTestClass // just a test class
{
public:
    MyTestClass() : data(0.12345){}
    friend ostream& operator<<(ostream& os,const MyTestClass& myClass);
private:
    double data;
};

ostream& operator<<(ostream& os,const MyTestClass& myClass) // print test class
{ return os<<myClass.data;}


int main()
{
    int i=0;
    string s1=(streamer.clear()<<"foo"<<"bar"<<"test").str();                      // test strings
    string s2=(streamer.clear()<<"i:"<<i++<<" "<<i++<<" "<<i++<<" "<<0.666).str(); // test numbers
    string s3=(streamer.clear()<<"test class:"<<MyTestClass()).str();              // test with test class
    cout<<"s1: '"<<s1<<"'"<<endl;
    cout<<"s2: '"<<s2<<"'"<<endl;
    cout<<"s3: '"<<s3<<"'"<<endl;
}

2

Voici la solution one-liner:

#include <iostream>
#include <string>

int main() {
  std::string s = std::string("Hi") + " there" + " friends";
  std::cout << s << std::endl;

  std::string r = std::string("Magic number: ") + std::to_string(13) + "!";
  std::cout << r << std::endl;

  return 0;
}

Bien que ce soit un peu moche, je pense que c'est à peu près aussi propre que votre chat en C ++.

Nous convertissons le premier argument en a std::stringet utilisons ensuite l'ordre d'évaluation (de gauche à droite) de operator+pour nous assurer que son opérande de gauche est toujours a std::string. De cette manière, nous concaténons le std::stringà gauche avec l' const char *opérande à droite et retournons un autrestd::string , en cascade l'effet.

Note: il y a quelques options pour l'opérande de droite, y compris const char *, std::stringetchar .

C'est à vous de décider si le nombre magique est 13 ou 6227020800.


Ah, vous oubliez, @Apollys, le nombre magique universel est 42.: D
Mr.Zeus


1

Si vous souhaitez l'utiliser, c++11vous pouvez utiliser des chaînes littérales définies par l'utilisateur et définir deux modèles de fonctions qui surchargent l'opérateur plus pour un std::stringobjet et tout autre objet. Le seul piège est de ne pas surcharger les opérateurs plus de std::string, sinon le compilateur ne sait pas quel opérateur utiliser. Vous pouvez le faire en utilisant le modèle std::enable_ifdetype_traits . Après cela, les chaînes se comportent comme en Java ou C #. Voir mon exemple d'implémentation pour plus de détails.

Code principal

#include <iostream>
#include "c_sharp_strings.hpp"

using namespace std;

int main()
{
    int i = 0;
    float f = 0.4;
    double d = 1.3e-2;
    string s;
    s += "Hello world, "_ + "nice to see you. "_ + i
            + " "_ + 47 + " "_ + f + ',' + d;
    cout << s << endl;
    return 0;
}

Fichier c_sharp_strings.hpp

Incluez ce fichier d'en-tête dans tous les endroits où vous souhaitez avoir ces chaînes.

#ifndef C_SHARP_STRING_H_INCLUDED
#define C_SHARP_STRING_H_INCLUDED

#include <type_traits>
#include <string>

inline std::string operator "" _(const char a[], long unsigned int i)
{
    return std::string(a);
}

template<typename T> inline
typename std::enable_if<!std::is_same<std::string, T>::value &&
                        !std::is_same<char, T>::value &&
                        !std::is_same<const char*, T>::value, std::string>::type
operator+ (std::string s, T i)
{
    return s + std::to_string(i);
}

template<typename T> inline
typename std::enable_if<!std::is_same<std::string, T>::value &&
                        !std::is_same<char, T>::value &&
                        !std::is_same<const char*, T>::value, std::string>::type
operator+ (T i, std::string s)
{
    return std::to_string(i) + s;
}

#endif // C_SHARP_STRING_H_INCLUDED

1

Quelque chose comme ça fonctionne pour moi

namespace detail {
    void concat_impl(std::ostream&) { /* do nothing */ }

    template<typename T, typename ...Args>
    void concat_impl(std::ostream& os, const T& t, Args&&... args)
    {
        os << t;
        concat_impl(os, std::forward<Args>(args)...);
    }
} /* namespace detail */

template<typename ...Args>
std::string concat(Args&&... args)
{
    std::ostringstream os;
    detail::concat_impl(os, std::forward<Args>(args)...);
    return os.str();
}
// ...
std::string s{"Hello World, "};
s = concat(s, myInt, niceToSeeYouString, myChar, myFoo);

1

Sur la base des solutions ci-dessus, j'ai créé une classe var_string pour mon projet afin de me faciliter la vie. Exemples:

var_string x("abc %d %s", 123, "def");
std::string y = (std::string)x;
const char *z = x.c_str();

La classe elle-même:

#include <stdlib.h>
#include <stdarg.h>

class var_string
{
public:
    var_string(const char *cmd, ...)
    {
        va_list args;
        va_start(args, cmd);
        vsnprintf(buffer, sizeof(buffer) - 1, cmd, args);
    }

    ~var_string() {}

    operator std::string()
    {
        return std::string(buffer);
    }

    operator char*()
    {
        return buffer;
    }

    const char *c_str()
    {
        return buffer;
    }

    int system()
    {
        return ::system(buffer);
    }
private:
    char buffer[4096];
};

Vous vous demandez toujours s'il y aura quelque chose de mieux en C ++?


1

En c11:

void printMessage(std::string&& message) {
    std::cout << message << std::endl;
    return message;
}

cela vous permet de créer un appel de fonction comme ceci:

printMessage("message number : " + std::to_string(id));

imprimera: numéro du message: 10


0

vous pouvez également "étendre" la classe de chaîne et choisir l'opérateur que vous préférez (<<, &, |, etc ...)

Voici le code utilisant l'opérateur << pour montrer qu'il n'y a pas de conflit avec les flux

note: si vous décommentez s1.reserve (30), il n'y a que 3 requêtes d'opérateur new () (1 pour s1, 1 pour s2, 1 pour reserve; vous ne pouvez pas réserver au moment du constructeur malheureusement); sans réserve, s1 doit demander plus de mémoire à mesure qu'elle grandit, donc cela dépend du facteur de croissance de l'implémentation de votre compilateur (le mien semble être 1,5, 5 appels new () dans cet exemple)

namespace perso {
class string:public std::string {
public:
    string(): std::string(){}

    template<typename T>
    string(const T v): std::string(v) {}

    template<typename T>
    string& operator<<(const T s){
        *this+=s;
        return *this;
    }
};
}

using namespace std;

int main()
{
    using string = perso::string;
    string s1, s2="she";
    //s1.reserve(30);
    s1 << "no " << "sunshine when " << s2 << '\'' << 's' << " gone";
    cout << "Aint't "<< s1 << " ..." <<  endl;

    return 0;
}

0

Stringstream avec une simple macro de préprocesseur utilisant une fonction lambda semble bien:

#include <sstream>
#define make_string(args) []{std::stringstream ss; ss << args; return ss;}() 

puis

auto str = make_string("hello" << " there" << 10 << '$');

-1

Cela fonctionne pour moi:

#include <iostream>

using namespace std;

#define CONCAT2(a,b)     string(a)+string(b)
#define CONCAT3(a,b,c)   string(a)+string(b)+string(c)
#define CONCAT4(a,b,c,d) string(a)+string(b)+string(c)+string(d)

#define HOMEDIR "c:\\example"

int main()
{

    const char* filename = "myfile";

    string path = CONCAT4(HOMEDIR,"\\",filename,".txt");

    cout << path;
    return 0;
}

Production:

c:\example\myfile.txt

12
Un chaton pleure chaque fois que quelqu'un utilise des macros pour quelque chose de plus complexe que des gardes de code ou des constantes: P
Rui Marques

1
À côté des chatons malheureux: Pour chaque argument, un objet string est créé, ce qui n'est pas nécessaire.
SebastianK

2
a voté contre, car l'utilisation de macros est définitivement une mauvaise solution
dhaumann

cela me ferait tressaillir d'horreur même pour C, mais en C ++, c'est diabolique. @RuiMarques: dans quelles situations les macros sont-elles meilleures pour les constantes que a constou (si le stockage nul est une exigence) un enumserait?
underscore_d

@underscore_d question intéressante mais je n'ai pas de réponse. Peut-être que la réponse est aucune.
Rui Marques

-1

Avez-vous essayé d'éviter le + =? à la place, utilisez var = var + ... cela a fonctionné pour moi.

#include <iostream.h> // for string

string myName = "";
int _age = 30;
myName = myName + "Vincent" + "Thorpe" + 30 + " " + 2019;

J'utilise C ++ borland builder 6, et cela fonctionne très bien pour moi. n'oubliez pas d'inclure ces en#include <iostream.h> // string #include <system.hpp> // ansiString
vincent thorpe

+ = n'est pas surchargé pour ce cas, semble penser que vous aviez ajouté des nombres et non une chaîne de concaténation
vincent thorpe
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.