Wie verkette ich mehrere C ++ - Zeichenfolgen in einer Zeile?


150

C # verfügt über eine Syntaxfunktion, mit der Sie viele Datentypen in einer Zeile zusammenfassen können.

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

Was wäre das Äquivalent in C ++? Soweit ich sehen kann, müssten Sie alles in separaten Zeilen ausführen, da mit dem Operator + nicht mehrere Zeichenfolgen / Variablen unterstützt werden. Das ist in Ordnung, sieht aber nicht so ordentlich aus.

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

Der obige Code erzeugt einen Fehler.


4
Wie an anderer Stelle erläutert, liegt dies nicht daran, dass "mit dem Operator + nicht mehrere Zeichenfolgen / Variablen unterstützt werden", sondern daran, dass Sie versuchen char *, einander Zeiger hinzuzufügen . Das ist es, was den Fehler erzeugt - weil das Summieren von Zeigern unsinnig ist. Machen Sie, wie unten erwähnt, mindestens den 1. Operanden zu einem std::string, und es gibt überhaupt keinen Fehler.
underscore_d

Welcher Fehler wurde erzeugt?
Wolf

Antworten:


238
#include <sstream>
#include <string>

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

Schauen Sie sich diesen Guru Of The Week-Artikel von Herb Sutter an: The String Formatters of Manor Farm


6
Versuchen Sie dies:std::string s = static_cast<std::ostringstream&>(std::ostringstream().seekp(0) << "HelloWorld" << myInt << niceToSeeYouString).str();
Byzanz

41
ss << "Wow, die Verkettung von Zeichenfolgen in C ++ ist beeindruckend" << "oder nicht."
Joaerl

4
Nur um einen anderen Weg zu nennen: Verwenden mehrerer Anhänge: string s = string ("abc"). Append ("def"). Append (otherStrVar) .append (to_string (123));
Patricio Rossi

std::stringstream ss; ss << "Hello, world, " << myInt << niceToSeeYouString; std::string s = ss.str();ist so ziemlich einzeilig
Kotauskas

74

In 5 Jahren hat niemand erwähnt .append?

#include <string>

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

Weil es im Vergleich zum Hinzufügen eines Textes in einer Zeile umständlich ist.
Hi-Angel

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

16
@Jonny s.append("One").append(" expression");Vielleicht sollte ich das Original bearbeiten, um den Rückgabewert auf diese Weise zu verwenden?
Gleichnamig

5
@ SilverMöls Das OP deklariert sim entsprechenden C # -Code und in seinem nicht kompilierten C ++ - Code in einer anderen Zeile. Sein gewünschtes C ++ ist s += "Hello world, " + "nice to see you, " + "or not.";das, was geschrieben werden kanns.append("Hello world, ").append("nice to see you, ").append("or not.");
gleichnamig

4
Ein Hauptvorteil von appendist, dass es auch funktioniert, wenn die Zeichenfolgen NUL-Zeichen enthalten.
John S.

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

Diese Zeichenarray-Literale sind keine C ++ std :: Strings - Sie müssen sie konvertieren:

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

Um Ints (oder einen anderen streambaren Typ) zu konvertieren, können Sie einen Boost lexical_cast verwenden oder Ihre eigene Funktion bereitstellen:

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

Sie können jetzt Dinge sagen wie:

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

16
Sie müssen nur den ersten explizit konvertieren: s + = string ("Hallo Welt") + "Schön, Sie zu sehen," + "oder nicht.";
Ferruccio

8
Ja, aber ich konnte es nicht ertragen zu erklären warum!

1
boost :: lexical_cast - schön und ähnlich auf Ihrer Str-Funktion :)
Bayda

2
Die Verkettungen rechts vom Konstruktor string("Hello world")werden über operator+()in der Klasse definiert ausgeführt string. Wenn stringder Ausdruck kein Objekt enthält, wird die Verkettung zu einer bloßen Summe von Zeichenzeigern char*.
David

41

Ihr Code geschrieben werden kann als 1 ,

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

... aber ich bezweifle, dass Sie danach suchen. In Ihrem Fall suchen Sie wahrscheinlich nach Streams:

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

1 " kann geschrieben werden als ": Dies funktioniert nur für String-Literale. Die Verkettung erfolgt durch den Compiler.


11
Ihr erstes Beispiel ist erwähnenswert, aber bitte erwähnen Sie auch, dass es nur zum "Verketten" von Literalzeichenfolgen funktioniert (der Compiler führt die Verkettung selbst durch).
j_random_hacker

Das erste Beispiel hat für mich einen Fehler ausgelöst, wenn eine Zeichenfolge zuvor wie folgt deklariert wurde const char smthg[] = "smthg": / Ist es ein Fehler?
Hi-Angel

@ Hi-Angel Leider nicht, stattdessen kannst du #definedeine Saite umgehen, obwohl dies seine eigenen Probleme mit sich bringt.
cz

27

Die Verwendung von benutzerdefinierten C ++ 14-Literalen und std::to_stringdem Code wird einfacher.

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;

Beachten Sie, dass die Verkettung von Zeichenfolgenliteralen zur Kompilierungszeit erfolgen kann. Entfernen Sie einfach die +.

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

2
Seit C ++ 11 können Sie std :: to_string
Patricio Rossi

Benutzerdefinierte Literale auch seit C ++ 11 <> . Ich habe bearbeitet.
Stapel Danny

@StackDanny Die Änderung ist falsch. Wenn ich "C ++ 14" sage, beziehe ich mich auf das std::literals::string_literals, nicht auf das Konzept einer UDL.
Rapptz

16

Um eine Lösung anzubieten, die mehr einzeilig ist: Eine Funktion concatkann implementiert werden, um die "klassische" Stringstream-basierte Lösung auf eine einzige Anweisung zu reduzieren . Es basiert auf verschiedenen Vorlagen und perfekter Weiterleitung.


Verwendung:

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

Implementierung:

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

Wäre dies nicht zum Aufblähen der Kompilierungszeit geworden, wenn es mehrere verschiedene Kombinationen in einer großen Codebasis gibt?
Shital Shah

1
@ShitalShah ist nicht mehr als nur das manuelle Inline-Schreiben, da diese Hilfsfunktionen ohnehin nur inline werden.
underscore_d

13

In C ++ 20 können Sie Folgendes tun:

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

Bis dahin können Sie dasselbe mit der {fmt} -Bibliothek tun :

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

Haftungsausschluss : Ich bin der Autor von {fmt}.


7

boost :: format

oder std :: stringstream

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

6

Das eigentliche Problem war, dass das Verketten von String-Literalen mit +in C ++ fehlschlägt:

string s;
s += "Hello world, " + "nice to see you, " + "or not.";
Der obige Code erzeugt einen Fehler.

In C ++ (auch in C) verketten Sie Zeichenfolgenliterale, indem Sie sie direkt nebeneinander platzieren:

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

Dies ist sinnvoll, wenn Sie Code in Makros generieren:

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

... ein einfaches Makro, das so verwendet werden kann

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

( Live-Demo ... )

oder, wenn Sie darauf bestehen, die +for-String-Literale zu verwenden (wie bereits von underscore_d vorgeschlagen ):

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

Eine andere Lösung kombiniert eine Zeichenfolge und eine const char*für jeden Verkettungsschritt

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

Ich benutze diese Technik auch oft, aber was ist, wenn eine oder mehrere Variablen int / string sind? .eg string s = "abc" "def" (int) y "ghi" (std :: string) z "1234"; dann ist sprintf immer noch die beste der schlechteren lösungen.
Bart Mensfort

@BartMensfort ist natürlich sprintfeine Option, aber es gibt auch std :: stringstream , das Probleme mit untergroßen Puffern verhindert.
Wolf


3

Sie müssten den Operator + () für jeden Datentyp definieren, den Sie mit der Zeichenfolge verknüpfen möchten. Da jedoch der Operator << für die meisten Typen definiert ist, sollten Sie std :: stringstream verwenden.

Verdammt, um 50 Sekunden geschlagen ...


1
Sie können keine neuen Operatoren für integrierte Typen wie char und int definieren.
Tyler McHenry

1
@ TylerMcHenry Nicht, dass ich es in diesem Fall empfehlen würde, aber Sie können sicherlich:std::string operator+(std::string s, int i){ return s+std::to_string(i); }
Eponymous

3

Wenn Sie das ausschreiben +=, sieht es fast genauso aus wie C #

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

3

Wie andere sagten, besteht das Hauptproblem mit dem OP-Code darin, dass der Bediener +nicht verkettet const char *; es funktioniert jedoch mit std::string.

Hier ist eine weitere Lösung, die C ++ 11-Lambdas verwendet for_eachund die separatorTrennung von Zeichenfolgen ermöglicht:

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

Verwendung:

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

Es scheint gut (linear) zu skalieren, zumindest nach einem kurzen Test auf meinem Computer; Hier ist ein kurzer Test, den ich geschrieben habe:

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

Ergebnisse (Millisekunden):

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

3

Vielleicht gefällt Ihnen meine "Streamer" -Lösung, um es wirklich in einer Zeile zu tun:

#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

Hier ist die Einzeiler-Lösung:

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

Obwohl es ein bisschen hässlich ist, denke ich, dass es ungefähr so ​​sauber ist, wie Sie es in C ++ bekommen.

Wir werfen das erste Argument auf a std::stringund verwenden dann die (von links nach rechts) Bewertungsreihenfolge von, operator+um sicherzustellen, dass der linke Operand immer a ist std::string. Auf diese Weise verketten wir std::stringdas links mit dem const char *Operanden rechts und geben einen anderen zurück std::string, wodurch der Effekt kaskadiert wird.

Hinweis: es gibt ein paar Optionen für den rechten Operanden, einschließlich const char *, std::stringund char.

Es liegt an Ihnen zu entscheiden, ob die magische Zahl 13 oder 6227020800 ist.


Ah, Sie vergessen, @Apollys, die universelle magische Zahl ist 42 .: D
Mr.Zeus


1

Wenn Sie bereit sind, können c++11Sie benutzerdefinierte Zeichenfolgenliterale verwenden und zwei Funktionsvorlagen definieren, die den Plus-Operator für ein std::stringObjekt und jedes andere Objekt überladen . Die einzige Gefahr besteht darin, die Plus-Operatoren von nicht zu überladen std::string, da der Compiler sonst nicht weiß, welchen Operator er verwenden soll. Sie können dies tun, indem Sie die Vorlage std::enable_ifvon verwenden type_traits. Danach verhalten sich Strings wie in Java oder C #. Einzelheiten finden Sie in meiner Beispielimplementierung.

Haupt code

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

Datei c_sharp_strings.hpp

Fügen Sie diese Header-Datei an allen Stellen ein, an denen Sie diese Zeichenfolgen haben möchten.

#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

So etwas funktioniert bei mir

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

Basierend auf den oben genannten Lösungen habe ich eine Klasse var_string für mein Projekt erstellt, um das Leben einfacher zu machen. Beispiele:

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

Die Klasse selbst:

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

Sie fragen sich immer noch, ob es in C ++ etwas Besseres geben wird?


1

In c11:

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

Auf diese Weise können Sie einen Funktionsaufruf wie folgt erstellen:

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

wird gedruckt: Nachrichtennummer: 10


0

Sie können auch die Zeichenfolgenklasse "erweitern" und den gewünschten Operator auswählen (<<, &, |, etc ...).

Hier ist der Code, der den Operator << verwendet, um anzuzeigen, dass kein Konflikt mit Streams besteht

Hinweis: Wenn Sie s1.reserve (30) auskommentieren, gibt es nur 3 neue () Operatoranforderungen (1 für s1, 1 für s2, 1 für Reserve; Sie können leider nicht zur Konstruktorzeit reservieren). Ohne Vorbehalt muss s1 mehr Speicher anfordern, wenn er wächst. Dies hängt also vom Wachstumsfaktor Ihrer Compiler-Implementierung ab (meiner scheint in diesem Beispiel 1,5, 5 neue () Aufrufe zu sein.)

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 mit einem einfachen Präprozessor-Makro, das eine Lambda-Funktion verwendet, scheint nett zu sein:

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

und dann

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

-1

Das funktioniert bei mir:

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

Ausgabe:

c:\example\myfile.txt

12
Ein Kätzchen weint jedes Mal, wenn jemand Makros für etwas Komplexeres als Code Guards oder Konstanten verwendet: P
Rui Marques

1
Neben unglücklichen Kätzchen: Für jedes Argument wird ein String-Objekt erstellt, das nicht notwendig ist.
SebastianK

2
Downvoted, da die Verwendung von Makros definitiv eine schlechte Lösung ist
Dhaumann

das würde mich selbst für C vor Entsetzen zurückschrecken lassen, aber in C ++ ist es teuflisch. @RuiMarques: In welchen Situationen sind Makros für Konstanten besser als ein constoder (wenn kein Speicher erforderlich ist) ein enum?
underscore_d

@underscore_d interessante Frage, aber ich habe keine Antwort darauf. Vielleicht ist die Antwort keine.
Rui Marques

-1

Haben Sie versucht, das + = zu vermeiden? Verwenden Sie stattdessen var = var + ... es hat bei mir funktioniert.

#include <iostream.h> // for string

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

Ich benutze C ++ Borland Builder 6 und es funktioniert gut für mich. Vergessen Sie nicht, diese #include <iostream.h> // string #include <system.hpp> // ansiString
Überschriften einzuschließen

+ = ist für diesen Fall nicht überladen, scheint zu glauben, Sie hätten Zahlen hinzugefügt und keine Zeichenfolge verkettet
Vincent Thorpe
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.