Jak połączyć wiele ciągów C ++ w jednym wierszu?


150

C # ma funkcję składni, w której można łączyć wiele typów danych w jednym wierszu.

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

Jaki byłby odpowiednik w C ++? O ile widzę, musiałbyś to wszystko zrobić w osobnych wierszach, ponieważ nie obsługuje wielu ciągów / zmiennych z operatorem +. To jest w porządku, ale nie wygląda tak schludnie.

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

Powyższy kod powoduje błąd.


4
Jak wyjaśniono w innym miejscu, nie dzieje się tak dlatego, że „nie obsługuje wielu łańcuchów / zmiennych z operatorem +” - ale raczej dlatego, że próbujesz dodawać char *do siebie wskaźniki. To właśnie generuje błąd - ponieważ sumowanie wskaźników jest bezsensowne. Jak zauważono poniżej, zmień co najmniej pierwszy operand na an std::stringi nie ma żadnego błędu.
underscore_d

Który błąd wystąpił?
Wolf

Odpowiedzi:


240
#include <sstream>
#include <string>

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

Zapoznaj się z artykułem Guru Tygodnia autorstwa Herb Sutter: The String Formatters of Manor Farm


6
Spróbuj tego:std::string s = static_cast<std::ostringstream&>(std::ostringstream().seekp(0) << "HelloWorld" << myInt << niceToSeeYouString).str();
Bizancjum

42
ss << "Wow, konkatenacja ciągów znaków w C ++ robi wrażenie" << "lub nie."
joaerl

4
Żeby nazwać inaczej: używając wielu 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();jest prawie jednoliniowy
Kotauskas

74

Za 5 lat nikt nie wspomniał .append?

#include <string>

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

Ponieważ jest to uciążliwe w porównaniu z samym dodawaniem tekstu w jednej linii.
Hi-Angel,

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

16
@Jonny s.append("One").append(" expression");Może powinienem edytować oryginał, aby w ten sposób używał wartości zwracanej?
Tytułowy

5
@ SilverMöls OP deklaruje sw innym wierszu w równoważnym kodzie C # oraz w swoim niekompilującym się kodzie C ++. Jego pożądany C ++ to s += "Hello world, " + "nice to see you, " + "or not.";coś, co można napisaćs.append("Hello world, ").append("nice to see you, ").append("or not.");
tytułowy

4
Główną zaletą programu appendjest to, że działa również, gdy łańcuchy zawierają znaki NUL.
John S.

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

Te literały tablicy znaków nie są C ++ std :: strings - musisz je przekonwertować:

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

Aby przekonwertować wartości typu int (lub dowolny inny typ nadający się do przesyłania strumieniowego), możesz użyć doładowania lexical_cast lub zapewnić własną funkcję:

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

Możesz teraz powiedzieć na przykład:

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

16
Musisz tylko jawnie przekonwertować pierwszy: s + = string ("Hello world,") + "nice to see you," + "or not.";
Ferruccio

8
Tak, ale nie potrafiłem wytłumaczyć dlaczego!

1
boost :: lexical_cast - ładne i podobne w twojej funkcji Str :)
bayda

2
Łączenia wykonywane po prawej stronie konstruktora string("Hello world")są wykonywane za pomocą operator+()zdefiniowanego w klasie string. Jeśli stringw wyrażeniu nie ma przedmiotu, konkatenacja staje się zwykłą sumą wskaźników znaków char*.
davide

41

Twój kod można zapisać jako 1 ,

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

... ale wątpię, że tego właśnie szukasz. W twoim przypadku prawdopodobnie szukasz strumieni:

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

1 " można zapisać jako ": Działa to tylko w przypadku literałów łańcuchowych. Łączenie jest wykonywane przez kompilator.


11
Twój pierwszy przykład jest warty wspomnienia, ale proszę również wspomnieć, że działa on tylko w przypadku „konkatenacji” literałów (kompilator sam wykonuje konkatenację).
j_random_hacker

Pierwszy przykład wywołał u mnie błąd, jeśli wcześniej zadeklarowano ciąg znaków np . const char smthg[] = "smthg": / Czy to błąd?
Hi-Angel,

@ Hi-Angel Niestety nie, zamiast tego możesz użyć #defineswojego ciągu znaków, aby to obejść, chociaż powoduje to własne problemy.
cz

27

Używanie literałów zdefiniowanych przez użytkownika w C ++ 14 i std::to_stringkod staje się łatwiejszy.

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;

Zauważ, że konkatenację literałów ciągów można wykonać w czasie kompilacji. Po prostu usuń +.

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

2
Od C ++ 11 możesz używać std :: to_string
Patricio Rossi

Literały zdefiniowane przez użytkownika również od C ++ 11 <> . Edytowałem.
Stack Danny

@StackDanny Zmiana jest zła. Kiedy mówię „C ++ 14”, odnoszę się do std::literals::string_literalspojęcia UDL, a nie do niego.
Rapptz

16

Aby zaoferować rozwiązanie, które jest bardziej concatjednokreskowe : Można zaimplementować funkcję, aby zredukować „klasyczne” rozwiązanie oparte na ciągach znaków do pojedynczej instrukcji . Opiera się na zróżnicowanych szablonach i perfekcyjnym spedycji.


Stosowanie:

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

Realizacja:

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

czy nie stałoby się to rozdęciem czasu kompilacji, gdyby istniało kilka różnych kombinacji w dużej bazie kodu.
Shital Shah

1
@ShitalShah to nic więcej niż tylko ręczne pisanie tych rzeczy, ponieważ te funkcje pomocnicze i tak zostaną wbudowane.
underscore_d

13

W C ++ 20 będziesz potrafił:

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

Do tego czasu możesz zrobić to samo z biblioteką {fmt} :

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

Zastrzeżenie : jestem autorem {fmt}.


7

boost :: format

lub std :: stringstream

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

6

Rzeczywistym problemem było to, że złączenie literały ciągów +nie w C ++:

string s;
s += "Hello world, " + "nice to see you, " + "or not.";
Powyższy kod powoduje błąd.

W C ++ (również w C) łączysz literały ciągów, umieszczając je tuż obok siebie:

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

Ma to sens, jeśli generujesz kod w makrach:

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

... proste makro, którego można używać w ten sposób

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

( demo na żywo ... )

lub, jeśli nalegasz na użycie +literałów ciągów for (jak już zasugerowano przez podkreślenie_d ):

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

Inne rozwiązanie łączy ciąg i znak const char*dla każdego kroku konkatenacji

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

Często używam tej techniki, ale co, jeśli jedna lub więcej zmiennych to int / string? .eg string s = "abc" "def" (int) y "ghi" (std :: string) z "1234"; wtedy sprintf jest nadal najlepszym z gorszych rozwiązań.
Bart Mensfort

@BartMensfort sprintfjest oczywiście opcją, ale jest też std :: stringstream, który zapobiega problemom z niewymiarowymi buforami.
Wolf


3

Musiałbyś zdefiniować operator + () dla każdego typu danych, który chciałbyś skoncentrować w łańcuchu, ale ponieważ operator << jest zdefiniowany dla większości typów, powinieneś użyć std :: stringstream.

Cholera, pobij o 50 sekund ...


1
W rzeczywistości nie można definiować nowych operatorów na typach wbudowanych, takich jak char i int.
Tyler McHenry

1
@TylerMcHenry Nie żebym go polecał w tym przypadku, ale z pewnością możesz:std::string operator+(std::string s, int i){ return s+std::to_string(i); }
Tytułowy

3

Jeśli napiszesz +=, wygląda prawie tak samo jak C #

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

3

Jak powiedzieli inni, głównym problemem z kodem OP jest to, że operator +nie konkatenuje const char *; ale to działa std::string.

Oto inne rozwiązanie, które używa lambd C ++ 11 for_eachi pozwala separatorna rozdzielenie ciągów znaków:

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

Stosowanie:

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

Wydaje się, że skaluje się dobrze (liniowo), przynajmniej po szybkim teście na moim komputerze; oto krótki test, który napisałem:

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

Wyniki (w milisekundach):

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

3

Może podoba Ci się moje rozwiązanie „Streamer”, które naprawdę robi to w jednej linii:

#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

Oto rozwiązanie jednowierszowe:

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

Chociaż jest trochę brzydki, myślę, że jest mniej więcej tak czysty jak kot w C ++.

Rzucamy pierwszy argument na a, std::stringa następnie używamy kolejności oceny (od lewej do prawej), operator+aby upewnić się, że jego lewy argument jest zawsze a std::string. W ten sposób łączymy operand po std::stringlewej stronie z const char *operandem po prawej stronie i zwracamy inny std::string, kaskadowo efekt.

Uwaga: istnieje kilka opcji dla prawego argumentu, w tym const char *, std::stringi char.

Do Ciebie należy decyzja, czy magiczna liczba to 13 czy 6227020800.


Ach, zapomniałeś, @Apollys, uniwersalna magiczna liczba to 42.: D
Pan Zeus


1

Jeśli chcesz użyć c++11, możesz użyć literałów ciągów zdefiniowanych przez użytkownika i zdefiniować dwa szablony funkcji, które przeciążają operator plus dla std::stringobiektu i dowolnego innego obiektu. Jedyną pułapką jest nieprzeciążanie operatorów plus std::string, w przeciwnym razie kompilator nie wie, którego operatora użyć. Możesz to zrobić za pomocą szablonu std::enable_ifz type_traits. Po tym ciągi zachowują się tak jak w Javie lub C #. Zobacz moją przykładową implementację po szczegóły.

Kod główny

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

Plik c_sharp_strings.hpp

Dołącz ten plik nagłówkowy we wszystkich miejscach, w których chcesz mieć te ciągi.

#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

Coś takiego działa na mnie

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

W oparciu o powyższe rozwiązania stworzyłem klasę var_string dla mojego projektu, aby ułatwić życie. Przykłady:

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

Sama klasa:

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

Wciąż zastanawiasz się, czy w C ++ będzie coś lepszego?


1

W c11:

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

pozwala to na tworzenie wywołań funkcji w ten sposób:

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

wypisze: numer wiadomości: 10


0

możesz także "rozszerzyć" klasę string i wybrać preferowany operator (<<, &, |, etc ...)

Oto kod wykorzystujący operator <<, aby pokazać, że nie ma konfliktu ze strumieniami

uwaga: jeśli odkomentujesz s1.reserve (30), są tylko 3 żądania operatora new () (1 dla s1, 1 dla s2, 1 dla rezerwacji; niestety nie możesz zarezerwować w czasie konstruktora); bez zastrzeżeń, s1 musi żądać więcej pamięci, gdy rośnie, więc zależy to od współczynnika wzrostu implementacji twojego kompilatora (w tym przykładzie wydaje się, że moje jest 1,5, 5 wywołań new ())

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

Strumień łańcuchowy z prostym makrem preprocessora używającym funkcji lambda wydaje się fajny:

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

i wtedy

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

-1

To działa dla mnie:

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

Wynik:

c:\example\myfile.txt

12
Kociak płacze za każdym razem, gdy ktoś używa makr do czegoś bardziej złożonego niż ochrona kodu lub stałe: P
Rui Marques

1
Oprócz nieszczęśliwych kociąt: dla każdego argumentu tworzony jest obiekt typu string, który nie jest konieczny.
SebastianK

2
odrzucone, ponieważ używanie makr jest zdecydowanie złym rozwiązaniem
dhaumann

wzdrygnęłam się z przerażenia nawet dla C, ale w C ++ jest to diaboliczne. @RuiMarques: w jakich sytuacjach makra są lepsze dla stałych niż a constlub (jeśli wymagana jest zerowa pamięć masowa) enum?
underscore_d

@underscore_d ciekawe pytanie, ale nie mam na nie odpowiedzi. Może odpowiedź brzmi: żadna.
Rui Marques

-1

Czy próbowałeś uniknąć + =? zamiast tego użyj var = var + ... to zadziałało dla mnie.

#include <iostream.h> // for string

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

Używam C ++ borland builder 6 i działa dobrze dla mnie. nie zapomnij dołączyć tych nagłówków#include <iostream.h> // string #include <system.hpp> // ansiString
vincent thorpe,

+ = nie jest przeciążony w tym przypadku, wydaje się, że masz dodawanie liczb, a nie łączenie ciągu
vincent Thorpe
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.