¿Cómo concateno múltiples cadenas de C ++ en una línea?

150

C # tiene una función de sintaxis donde puede concatenar muchos tipos de datos en una sola línea.

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

¿Cuál sería el equivalente en C ++? Hasta donde puedo ver, tendrías que hacerlo todo en líneas separadas, ya que no admite múltiples cadenas / variables con el operador +. Esto está bien, pero no se ve tan bien.

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

El código anterior produce un error.

Nick Bolton
fuente
44
Como se explicó en otra parte, esto no se debe a que "no admite múltiples cadenas / variables con el operador +", sino porque está tratando de agregar char *punteros entre sí. Eso es lo que genera el error, porque sumar punteros no tiene sentido. Como se indica a continuación, convierta al menos el primer operando en un std::string, y no hay ningún error en absoluto.
underscore_d
¿Qué error se produjo?
Wolf

Respuestas:

239
#include <sstream>
#include <string>

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

Eche un vistazo a este artículo del Gurú de la semana de Herb Sutter: The String Formatters of Manor Farm

Paolo Tedesco
fuente
66
Pruebe esto:std::string s = static_cast<std::ostringstream&>(std::ostringstream().seekp(0) << "HelloWorld" << myInt << niceToSeeYouString).str();
Bizantino
41
ss << "Wow, la concatenación de cadenas en C ++ es impresionante" << "o no".
joaerl
44
Solo para nombrar de otra manera: usando múltiples 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();es más o menos una línea
Kotauskas
74

En 5 años nadie ha mencionado .append?

#include <string>

std::string s;
s.append("Hello world, ");
s.append("nice to see you, ");
s.append("or not.");
Michel
fuente
Porque es engorroso en comparación con solo agregar un texto en una línea.
Hola ángel,
11
s.append("One"); s.append(" line");
Jonny
16
@ Jonny ¿ s.append("One").append(" expression");Quizás debería editar el original para usar el valor de retorno de esta manera?
Epónimo
55
@ SilverMöls El OP declara sen una línea diferente en el código C # equivalente y en su código C ++ que no se compila. Su C ++ deseado es el s += "Hello world, " + "nice to see you, " + "or not.";que se puede escribirs.append("Hello world, ").append("nice to see you, ").append("or not.");
Epónimo
44
Una ventaja importante appendes que también funciona cuando las cadenas contienen caracteres NUL.
John S.
62
s += "Hello world, " + "nice to see you, " + "or not.";

Esos literales de matriz de caracteres no son C ++ std :: strings; debe convertirlos:

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

Para convertir ints (o cualquier otro tipo de transmisión) puede usar un impulso lexical_cast o proporcionar su propia función:

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

Ahora puedes decir cosas como:

string s = string("The meaning is ") + Str( 42 );
Comunidad
fuente
16
Solo necesita convertir explícitamente el primero: s + = string ("Hola mundo") + "encantado de verte," + "o no";
Ferruccio
8
¡Sí, pero no podría soportar explicar por qué!
1
boost :: lexical_cast - agradable y similar en su función Str :)
bayda
2
Las concatenaciones hechas a la derecha del constructor string("Hello world")se realizan a través de operator+()definido en la clase string. Si no hay ningún stringobjeto en la expresión, la concatenación se convierte en una simple suma de punteros de caracteres char*.
davide
41

Su código puede escribirse como 1 ,

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

... pero dudo que sea lo que estás buscando. En su caso, probablemente esté buscando transmisiones:

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

1 " se puede escribir como ": esto solo funciona para literales de cadena. La concatenación la realiza el compilador.

John Dibling
fuente
11
Vale la pena mencionar su primer ejemplo, pero también mencione que funciona solo para "concatenar" cadenas literales (el compilador realiza la propia concatenación).
j_random_hacker
El primer ejemplo provocó un error para mí si una cadena se declaró previamente como, por ejemplo const char smthg[] = "smthg": / ¿Es un error?
Hola ángel,
@ Hi-Angel Desafortunadamente no, en su lugar, puede #defineusar su cadena para evitar esto, aunque esto trae sus propios problemas.
cz
27

Usando C ++ 14 literales definidos por el usuario y std::to_stringel código se vuelve más fácil.

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;

Tenga en cuenta que los literales de cadena de concatenación se pueden hacer en tiempo de compilación. Solo quite el +.

str += "Hello World, " "nice to see you, " "or not";
Rapptz
fuente
2
Desde C ++ 11 puede usar std :: to_string
Patricio Rossi el
literales definidos por el usuario también desde C ++ 11 <> . Yo edité.
Apila a Danny
@StackDanny El cambio está mal. Cuando digo "C ++ 14" me refiero a std::literals::string_literals, no al concepto de UDL.
Rapptz
16

Para ofrecer una solución que sea más de una línea: concatse puede implementar una función para reducir la solución "clásica" basada en cadenas a una sola declaración . Se basa en plantillas variadas y reenvío perfecto.


Uso:

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

Implementación:

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();
}
SebastianK
fuente
¿No se convertiría esto en una gran cantidad de tiempo de compilación si hay varias combinaciones diferentes en una base de código grande?
Shital Shah
1
@ShitalShah no es más que escribir esas cosas en línea de forma manual, ya que estas funciones auxiliares se pondrán en línea de todos modos.
underscore_d
13

En C ++ 20 podrás hacer:

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

Hasta entonces, podría hacer lo mismo con la biblioteca {fmt} :

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

Descargo de responsabilidad : soy el autor de {fmt}.

vitaut
fuente
7

impulso :: formato

o std :: stringstream

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

El problema real era que la concatenación de literales de cadena con +falla en C ++:

string s;
s += "Hello world, " + "nice to see you, " + "or not.";
El código anterior produce un error.

En C ++ (también en C), concatena los literales de cadena simplemente colocándolos uno al lado del otro:

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

Esto tiene sentido, si genera código en macros:

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

... una macro simple que se puede usar así

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

( demostración en vivo ... )

o, si insiste en usar los +literales de cadena for (como ya ha sido sugerido por underscore_d ):

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

Otra solución combina una cadena y una const char*para cada paso de concatenación

string s;
s += "Hello world, "
s += "nice to see you, "
s += "or not.";
Lobo
fuente
También uso mucho esta técnica, pero ¿qué pasa si una o más variables son int / string? .eg cadena s = "abc" "def" (int) y "ghi" (std :: string) z "1234"; entonces, sprintf sigue siendo la mejor de las peores soluciones.
Bart Mensfort el
@BartMensfort, por supuesto, sprintfes una opción, pero también hay std :: stringstream que evita problemas con buffers de menor tamaño.
Wolf
5
auto s = string("one").append("two").append("three")
Shital Shah
fuente
3

Tendría que definir el operador + () para cada tipo de datos que desea concenatear a la cadena, sin embargo, dado que el operador << está definido para la mayoría de los tipos, debe usar std :: stringstream.

Maldición, venció por 50 segundos ...

tstenner
fuente
1
En realidad, no puede definir nuevos operadores en tipos integrados como char e int.
Tyler McHenry
1
@TylerMcHenry No es que yo recomiendo en este caso, pero que sin duda puede:std::string operator+(std::string s, int i){ return s+std::to_string(i); }
Eponymous
3

Si escribe el +=, se ve casi igual que C #

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

Como otros dijeron, el principal problema con el código OP es que el operador +no concatena const char *; funciona con std::string, sin embargo.

Aquí hay otra solución que usa lambdas C ++ 11 for_eachy permite proporcionar una separatorpara separar las cadenas:

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

Uso:

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

Parece escalar bien (linealmente), al menos después de una prueba rápida en mi computadora; Aquí hay una prueba rápida que he escrito:

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

Resultados (milisegundos):

join(10)   : 2
join(100)  : 10
join(1000) : 91
join(10000): 898
elnigno
fuente
3

Tal vez te guste mi solución "Streamer" para hacerlo realmente en una línea:

#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;
}
bterwijn
fuente
2

Aquí está la solución de una línea:

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

Aunque es un poco feo, creo que es lo más limpio que puedes tener en C ++.

Estamos lanzando el primer argumento a a std::stringy luego usando el orden de evaluación (de izquierda a derecha) de operator+para asegurarnos de que su operando izquierdo sea ​​siempre a std::string. De esta manera, concatenamos el std::stringde la izquierda con el const char *operando a la derecha y devolvemos otro std::string, conectando en cascada el efecto.

Nota: hay algunas opciones para el operando derecho, incluyendo const char *, std::stringy char.

Depende de usted decidir si el número mágico es 13 o 6227020800.

Apollys apoya a Monica
fuente
Ah, olvídate, @Apollys, el número mágico universal es 42.: D
Mr.Zeus
1

Puede usar este encabezado para este respecto: https://github.com/theypsilon/concat

using namespace concat;

assert(concat(1,2,3,4,5) == "12345");

Debajo del capó usarás un std :: ostringstream.

José Manuel
fuente
1

Si está dispuesto a utilizarlo c++11, puede utilizar literales de cadena definidos por el usuario y definir dos plantillas de función que sobrecarguen el operador más para un std::stringobjeto y cualquier otro objeto. El único inconveniente es no sobrecargar los operadores plus de std::string, de lo contrario el compilador no sabe qué operador usar. Puede hacerlo utilizando la plantilla std::enable_ifdetype_traits . Después de eso, las cadenas se comportan como en Java o C #. Vea mi ejemplo de implementación para más detalles.

Código 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;
}

Archivo c_sharp_strings.hpp

Incluya este archivo de encabezado en todos los lugares donde desea tener estas cadenas.

#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
Scindix
fuente
1

Algo como esto me funciona

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);
smoothware
fuente
1

Basado en las soluciones anteriores, hice una clase var_string para mi proyecto para facilitar la vida. Ejemplos:

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

La clase misma:

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

¿Todavía te preguntas si habrá algo mejor en C ++?

Bart Mensfort
fuente
1

En c11:

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

esto le permite crear llamadas a funciones como esta:

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

imprimirá: número de mensaje: 10

devcodexyz
fuente
0

También puede "extender" la clase de cadena y elegir el operador que prefiera (<<, &, |, etc ...)

Aquí está el código que usa el operador << para mostrar que no hay conflicto con las transmisiones

nota: si descomenta s1.reserve (30), solo hay 3 solicitudes de operador nuevas () (1 para s1, 1 para s2, 1 para reserva; desafortunadamente no puede reservar en el momento del constructor); sin reserva, s1 tiene que solicitar más memoria a medida que crece, por lo que depende de su factor de crecimiento de implementación del compilador (el mío parece ser 1.5, 5 llamadas nuevas () en este ejemplo)

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;
}
ddy
fuente
0

Stringstream con una simple macro de preprocesador que utiliza una función lambda parece agradable:

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

y entonces

auto str = make_string("hello" << " there" << 10 << '$');
Asikorski
fuente
-1

Esto funciona para mi:

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

Salida:

c:\example\myfile.txt
erik80
fuente
12
Un gatito llora cada vez que alguien usa macros para algo más complejo que guardias de código o constantes: P
Rui Marques
1
Además de gatitos infelices: para cada argumento se crea un objeto de cadena que no es necesario.
SebastianK
2
rechazado, ya que usar macros es definitivamente una mala solución
dhaumann
esto me haría estremecer de horror incluso para C, pero en C ++, es diabólico. @RuiMarques: ¿en qué situaciones las macros son mejores para las constantes que ao const(si se requiere cero almacenamiento) enumsería una?
underscore_d
@underscore_d pregunta interesante pero no tengo respuesta para eso. Tal vez la respuesta es ninguna.
Rui Marques
-1

¿Has tratado de evitar el + =? en su lugar use var = var + ... funcionó para mí.

#include <iostream.h> // for string

string myName = "";
int _age = 30;
myName = myName + "Vincent" + "Thorpe" + 30 + " " + 2019;
Vincent Thorpe
fuente
Yo uso C ++ borland builder 6, y funciona bien para mí. no olvides incluir estos encabezados#include <iostream.h> // string #include <system.hpp> // ansiString
vincent thorpe
+ = no está sobrecargado para este caso, parece pensar que ha agregado números y no concatena una cadena
vincent thorpe