Como concatenar várias seqüências de caracteres C ++ em uma linha?

150

O C # possui um recurso de sintaxe onde você pode concatenar vários tipos de dados juntos em uma linha.

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

Qual seria o equivalente em C ++? Tanto quanto eu posso ver, você teria que fazer tudo em linhas separadas, pois não suporta várias cadeias / variáveis ​​com o operador +. Tudo bem, mas não parece tão legal.

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

O código acima produz um erro.

Nick Bolton
fonte
4
Como explicado em outro lugar, isso não ocorre porque "ele não suporta várias cadeias / variáveis ​​com o operador +" - mas sim porque você está tentando adicionar char *ponteiros entre si. É isso que gera o erro - porque somar indicadores não faz sentido. Como observado abaixo, transforme pelo menos o 1º operando em um std::stringe não haverá nenhum erro.
Underscore_d
Qual erro foi produzido?
Wolf
Possível duplicata de Como concatenar um std :: string e um int?
Vitaut

Respostas:

239
#include <sstream>
#include <string>

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

Dê uma olhada neste artigo do Guru da Semana de Herb Sutter: Os Formadores de Cordas da Manor Farm

Paolo Tedesco
fonte
6
Tente isto:std::string s = static_cast<std::ostringstream&>(std::ostringstream().seekp(0) << "HelloWorld" << myInt << niceToSeeYouString).str();
Bizantiano
41
ss << "Uau, a concatenação de strings em C ++ é impressionante" << "ou não."
Joaerl
4
Apenas para citar outra maneira: usando vários anexos: 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();é praticamente uma linha
Kotauskas
74

Em 5 anos ninguém mencionou .append?

#include <string>

std::string s;
s.append("Hello world, ");
s.append("nice to see you, ");
s.append("or not.");
Michel
fonte
Porque é complicado comparar apenas adicionar um texto em uma linha.
21915 Oi-Angel
11
s.append("One"); s.append(" line");
27915 Jonny
16
@ Jonny s.append("One").append(" expression");Talvez eu deva editar o original para usar o valor de retorno dessa maneira?
Eponymous
5
@ SilverMöls O OP declara sem uma linha diferente no código C # equivalente e em seu código C ++ não compilável. Seu C ++ desejado é o s += "Hello world, " + "nice to see you, " + "or not.";que pode ser escrito #s.append("Hello world, ").append("nice to see you, ").append("or not.");
1919
4
Uma grande vantagem appenddisso é que ele também funciona quando as seqüências contêm caracteres NUL.
John S.
62
s += "Hello world, " + "nice to see you, " + "or not.";

Esses literais da matriz de caracteres não são C ++ std :: strings - você precisa convertê-los:

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

Para converter ints (ou qualquer outro tipo de streamable), você pode usar um impulso lexical_cast ou fornecer sua própria função:

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

Agora você pode dizer coisas como:

string s = string("The meaning is ") + Str( 42 );
Comunidade
fonte
16
Você só precisa converter explicitamente o primeiro: s + = string ("Hello world") + "prazer em vê-lo", "+" ou não. ";
287 Ferruccio
8
Sim, mas não pude deixar de explicar o porquê!
1
boost :: lexical_cast - agradável e semelhante em sua função Str :)
bayda
2
As concatenações feitas à direita do construtor string("Hello world")são executadas operator+()conforme definido na classe string. Se não houver stringobjeto na expressão, a concatenação se tornará uma mera soma de ponteiros de caracteres char*.
Davide
41

Seu código pode ser escrito como 1 ,

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

... mas duvido que seja isso que você está procurando. No seu caso, você provavelmente está procurando fluxos:

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

1 " pode ser escrito como ": isso funciona apenas para literais de string. A concatenação é feita pelo compilador.

John Dibling
fonte
11
Vale a pena mencionar o seu 1º exemplo, mas mencione também que ele funciona apenas para "concatenar" cadeias literais (o compilador executa a concatenação).
j_random_hacker
O primeiro exemplo acionou um erro para mim se uma string foi declarada anteriormente como por exemplo const char smthg[] = "smthg": / É um bug?
Hi-Angel
@ Hi-Angel Infelizmente, você não pode #defineusar sua string para contornar isso, embora isso traga seus próprios problemas.
cz
27

Usando literais definidos pelo usuário C ++ 14 e std::to_stringo código se torna mais 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;

Observe que a concatenação de literais de string pode ser feita em tempo de compilação. Basta remover o +.

str += "Hello World, " "nice to see you, " "or not";
Rapptz
fonte
2
Desde C ++ 11 você pode usar std :: to_string
Patricio Rossi
literais definidos pelo usuário também desde C ++ 11 <> . Eu editei.
Stack Danny
@StackDanny A mudança está errada. Quando digo "C ++ 14", refiro-me ao std::literals::string_literals, não ao conceito de UDL.
Rapptz 04/04/19
16

Para oferecer uma solução com mais de uma linha: É concatpossível implementar uma função para reduzir a solução baseada em cadeia de caracteres "clássica" a uma única instrução . Baseia-se em modelos variados e encaminhamento perfeito.


Uso:

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

Implementação:

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
fonte
isso não se tornaria incômodo no tempo de compilação se houver várias combinações diferentes na grande base de código.
Shital Shah 8/02
1
O @ShitalShah não é mais do que escrever essas coisas manualmente manualmente, já que essas funções auxiliares serão incluídas de qualquer maneira.
Underscore_d
13

No C ++ 20, você poderá:

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

Até então, você poderia fazer o mesmo com a biblioteca {fmt} :

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

Isenção de responsabilidade : sou o autor de {fmt}.

vitaut
fonte
7

boost :: format

ou std :: stringstream

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

O problema real era que concatenar literais de string com +falhas em C ++:

string s;
s += "Hello world, " + "nice to see you, " + "or not.";
O código acima produz um erro.

No C ++ (também em C), você concatena literais de strings apenas colocando-os um ao lado do outro:

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

Isso faz sentido, se você gerar código em macros:

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

... uma macro simples que pode ser usada assim

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

( demonstração ao vivo ... )

ou, se você insistir em usar os +literais de string for (como já sugerido por underscore_d ):

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

Outra solução combina uma sequência e uma const char*para cada etapa da concatenação

string s;
s += "Hello world, "
s += "nice to see you, "
s += "or not.";
Lobo
fonte
Eu também uso muito essa técnica, mas e se uma ou mais variáveis ​​forem int / string? .eg string s = "abc" "def" (int) e "ghi" (std :: string) z "1234"; então, o sprintf ainda é o melhor das piores soluções.
22620 Bart Barton
@BartMensfort, sprintfé claro, é uma opção, mas também existe std :: stringstream que evita problemas com buffers de tamanho menor.
Lobo1 /
5
auto s = string("one").append("two").append("three")
Shital Shah
fonte
3

Você precisaria definir o operador + () para todos os tipos de dados que você gostaria de concenatar com a string, mas como o operador << é definido para a maioria dos tipos, você deve usar std :: stringstream.

Droga, bata em 50 segundos ...

tstenner
fonte
1
Você não pode realmente definir novos operadores em tipos internos, como char e int.
Tyler McHenry
1
@TylerMcHenry Não que eu recomende neste caso, mas você certamente pode:std::string operator+(std::string s, int i){ return s+std::to_string(i); }
Eponymous
3

Se você escrever o +=, parece quase o mesmo que C #

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

Como outros disseram, o principal problema com o código OP é que o operador +não concatena const char *; funciona com std::string, no entanto.

Aqui está outra solução que usa lambdas C ++ 11 for_eache permite fornecer um separatorpara separar as strings:

#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 bem (linearmente), pelo menos depois de um teste rápido no meu computador; aqui está um teste rápido que eu escrevi:

#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 (milissegundos):

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

Talvez você goste da minha solução "Streamer" para realmente fazer isso em uma linha:

#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
fonte
2

Aqui está a solução de uma linha:

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

Embora seja um pouco feio, acho que é o mais limpo que você consegue em C ++.

Estamos lançando o primeiro argumento para a std::stringe, em seguida, usando a ordem de avaliação (da esquerda para a direita) de operator+para garantir que seu operando esquerdo seja sempre a std::string. Dessa maneira, concatenamos o std::stringda esquerda com o const char *operando do lado direito e retornamos outro std::string, fazendo o efeito cascata.

Nota: existem algumas opções para o operando direito, incluindo const char *, std::stringe char.

Cabe a você decidir se o número mágico é 13 ou 6227020800.

Apollys apoia Monica
fonte
Ah, você esquece, @Apollys, o número mágico universal é 42.: D
Mr.Zeus
1

Você pode usar este cabeçalho para esse assunto: https://github.com/theypsilon/concat

using namespace concat;

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

Sob o capô, você usará um std :: ostringstream.

José Manuel
fonte
1

Se você estiver disposto a usar, c++11poderá utilizar literais de string definidos pelo usuário e definir dois modelos de função que sobrecarregam o operador plus de um std::stringobjeto e qualquer outro objeto. A única armadilha é não sobrecarregar os operadores plus std::string, caso contrário, o compilador não sabe qual operador usar. Você pode fazer isso usando o modelo std::enable_ifde type_traits. Depois disso, as strings se comportam como em Java ou C #. Veja meu exemplo de implementação para obter detalhes.

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

Arquivo c_sharp_strings.hpp

Inclua esse arquivo de cabeçalho em todos os lugares em que você deseja ter essas strings.

#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
fonte
1

Algo assim funciona para mim

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);
talheres
fonte
1

Com base nas soluções acima, criei uma classe var_string para o meu projeto para facilitar a vida. Exemplos:

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

A classe em si:

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

Ainda querendo saber se haverá algo melhor em C ++?

Bart Mensfort
fonte
1

Em c11:

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

isso permite que você crie uma chamada de função como esta:

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

imprimirá: número da mensagem: 10

devcodexyz
fonte
0

você também pode "estender" a classe de string e escolher o operador de sua preferência (<<, &, |, etc ...)

Aqui está o código usando o operador << para mostrar que não há conflito com fluxos

nota: se você descomentar s1.reserve (30), há apenas 3 novas solicitações de operador () (1 para s1, 1 para s2, 1 para reserva; infelizmente não é possível reservar no tempo do construtor); sem reserva, o s1 precisa solicitar mais memória à medida que cresce, portanto depende do fator de crescimento da implementação do compilador (o meu parece ser 1,5, 5 novas chamadas () neste exemplo)

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
fonte
0

Stringstream com uma macro simples de pré-processador usando uma função lambda parece bom:

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

e depois

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

Isso funciona para mim:

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

Resultado:

c:\example\myfile.txt
erik80
fonte
12
Um gatinho chora cada vez que alguém usa macros para algo mais complexo do que guardas de código ou constantes: P
Rui Marques
1
Ao lado de gatinhos infelizes: Para cada argumento, um objeto de string é criado, o que não é necessário.
Sebastiank
2
downvoted, uma vez usando macros é definitivamente uma má solução
dhaumann
isso me faria estremecer de horror, mesmo em C, mas em C ++ é diabólico. @RuiMarques: em quais situações as macros são melhores para constantes do que um constou (se o armazenamento zero for um requisito) enumseria?
Underscore_d
@underscore_d pergunta interessante, mas não tenho resposta para isso. Talvez a resposta seja nenhuma.
Rui Marques
-1

Você já tentou evitar o + =? em vez disso, use var = var + ... funcionou para mim.

#include <iostream.h> // for string

string myName = "";
int _age = 30;
myName = myName + "Vincent" + "Thorpe" + 30 + " " + 2019;
vincent thorpe
fonte
Eu uso o C ++ borland builder 6 e funciona bem para mim. não se esqueça de incluir este cabeçalho#include <iostream.h> // string #include <system.hpp> // ansiString
vincent thorpe 16/01/19
+ = Não está sobrecarregada para este caso, parecem pensar que você tinha a adição de números e não corda concatenate
vincent Thorpe