Birden çok C ++ dizesini bir satırda nasıl birleştiririm?


156

C #, birçok veri türünü 1 satırda birleştirebileceğiniz bir sözdizimi özelliğine sahiptir.

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

C ++ ile eşdeğeri ne olabilir? Görebildiğim kadarıyla, + operatörüyle birden çok dizeyi / değişkeni desteklemediği için hepsini ayrı satırlarda yapmanız gerekir. Sorun değil, ama pek de düzgün görünmüyor.

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

Yukarıdaki kod bir hata oluşturur.


4
Başka bir yerde açıklandığı gibi, bunun nedeni "+ operatörüyle birden çok dizeyi / değişkeni desteklememesi" değil char *, birbirinize işaretçiler eklemeye çalışmanızdır . Hatayı oluşturan budur - çünkü işaretçileri toplamak anlamsızdır. Aşağıda belirtildiği gibi, en azından 1. işleneni bir haline getirin std::stringve hiç hata yok.
underscore_d

Hangi hata üretildi?
Wolf

Yanıtlar:


249
#include <sstream>
#include <string>

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

Herb Sutter: The String Formatters of Manor Farm'ın bu Haftanın Gurusu makalesine bir göz atın


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

44
ss << "Vay canına, C ++ 'da dize birleştirme etkileyici" << "ya da değil."
joaerl

4
Sadece başka bir şekilde adlandırmak için: çoklu ekleme kullanma: 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();hemen hemen tek satırlık
Kotauskas

76

5 yıl içinde kimse bahsetmedi .appendmi?

#include <string>

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

Çünkü tek satırda bir metin eklemeye kıyasla zahmetlidir.
Hi-Angel

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

16
@Jonny s.append("One").append(" expression");Belki de dönüş değerini bu şekilde kullanmak için orijinali düzenlemeliyim?
Eponymous

5
@ SilverMöls OP s, eşdeğer C # kodunda ve derlenmeyen C ++ kodunda farklı bir satırda bildirir . İstediği C ++ s += "Hello world, " + "nice to see you, " + "or not.";yazılabilirs.append("Hello world, ").append("nice to see you, ").append("or not.");
Eponymous

4
Bunun önemli bir avantajı append, dizeler NUL karakterleri içerdiğinde de çalışmasıdır.
John S.

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

Bu karakter dizisi değişmezleri C ++ std :: strings değildir - bunları dönüştürmeniz gerekir:

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

İnt'leri (veya başka herhangi bir akışa uygun türü) dönüştürmek için lexical_cast'i kullanabilir veya kendi işlevinizi sağlayabilirsiniz:

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

Artık şöyle şeyler söyleyebilirsiniz:

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

16
Yalnızca ilkini açıkça dönüştürmeniz gerekir: s + = string ("Merhaba dünya") + "sizi görmek güzel," + "ya da değil.";
Ferruccio

8
Evet, ama nedenini açıklamakla yüzleşemedim!

1
boost :: lexical_cast - Str fonksiyonunuzda güzel ve benzer :)
bayda

3
Yapıcının sağ tarafında yapılan birleştirmeler , sınıfta tanımlananlar string("Hello world")aracılığıyla gerçekleştirilir . İfadede nesne yoksa, birleştirme yalnızca bir karakter işaretçileri toplamı haline gelir . operator+()stringstringchar*
davide

42

Kodunuz 1 olarak yazılabilir ,

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

... ama aradığınızın bu olduğundan şüpheliyim. Sizin durumunuzda, muhtemelen akışları arıyorsunuz:

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

1 " şu şekilde yazılabilir ": Bu yalnızca dizge değişmezleri için çalışır. Birleştirme, derleyici tarafından yapılır.


11
İlk örneğinizden bahsetmeye değer, ancak lütfen yalnızca değişmez dizeleri "birleştirmek" için çalıştığını da belirtin (derleyici birleştirmeyi kendisi gerçekleştirir).
j_random_hacker

İlk örnek, eğer bir dizge daha önce örneğin const char smthg[] = "smthg": / Bu bir hata mı?
Hi-Angel

@ Hi-Angel Ne yazık ki hayır, bunun yerine dizginizi bunun üstesinden gelebilirsiniz #define, ancak bu kendi sorunlarını da beraberinde getirir.
cz

28

C ++ 14 kullanıcı tanımlı değişmez değerleri kullanmak ve std::to_stringkod daha kolay hale gelir.

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;

Dize değişmezlerinin birleştirilmesinin derleme zamanında yapılabileceğini unutmayın. Sadece +.

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

2
C ++ 11'den beri std :: to_string
Patricio Rossi'yi

kullanıcı tanımlı değişmez değerler de C ++ 11 <> . Ben düzenledim.
Stack Danny

@StackDanny Değişiklik yanlış. "C ++ 14" std::literals::string_literalsdediğimde, UDL kavramına değil, kastediyorum.
Rapptz

16

Daha tek satırlık bir çözüm sunmak için: concat"Klasik" dizi akışı tabanlı çözümü tek bir ifadeye indirgemek için bir işlev uygulanabilir . Çeşitli şablonlara ve mükemmel yönlendirmeye dayanmaktadır.


Kullanım:

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

Uygulama:

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

Büyük kod tabanında birkaç farklı kombinasyon varsa, bu derleme zamanı şişmesi haline gelmez mi?
Shital Shah

1
@ShitalShah, bu şeyleri elle yazmaktan başka bir şey değil, çünkü bu yardımcı işlevler yine de satır içi olacak.
underscore_d

13

C ++ 20'de şunları yapabileceksiniz:

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

O zamana kadar aynısını {fmt} kitaplığıyla da yapabilirsiniz :

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

Sorumluluk reddi beyanı : {fmt} adlı kitabın yazarıyım.


7

boost :: format

veya std :: stringstream

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

6

Gerçek sorun dize hazır birleştirerek ile olmasıydı +C ++ başarısız:

string s;
s += "Hello world, " + "nice to see you, " + "or not.";
Yukarıdaki kod bir hata oluşturur.

C ++ 'da (C de), dize değişmezlerini sadece yan yana yerleştirerek birleştirirsiniz:

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

Makrolarda kod üretirseniz bu mantıklıdır:

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

... bunun gibi kullanılabilecek basit bir makro

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

( canlı demo ... )

veya +for string değişmezlerini kullanmakta ısrar ediyorsanız (zaten alt çizgi_d tarafından önerildiği gibi ):

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

Başka bir çözüm, const char*her birleştirme adımı için bir dizeyi ve a'yı birleştirir

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

Ben de bu tekniği çok kullanıyorum, ama ya bir veya daha fazla değişken int / string ise? .eg string s = "abc" "def" (int) y "ghi" (std :: string) z "1234"; o zaman sprintf hala daha kötü çözümlerin en iyisidir.
Bart Mensfort

@BartMensfort elbette sprintfbir seçenektir, ancak küçük tamponlarla ilgili sorunları önleyen std :: stringstream de vardır .
Wolf


3

Dizeye yoğunlaştırmak isteyeceğiniz her veri türü için + () işlecini tanımlamanız gerekir, ancak << işleci çoğu tür için tanımlandığından, std :: stringstream'i kullanmalısınız.

Lanet olsun, 50 saniye geç ...


1
Char ve int gibi yerleşik türlerde yeni operatörler tanımlayamazsınız.
Tyler McHenry

1
@TylerMcHenry Bu durumda tavsiye ettiğimden değil, ama kesinlikle şunları yapabilirsiniz:std::string operator+(std::string s, int i){ return s+std::to_string(i); }
Eponymous

3

Eğer yazarsanız +=, neredeyse C # ile aynı görünür

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

3

Başkalarının dediği gibi, OP koduyla ilgili temel sorun, operatörün +sıraya dizmemesidir const char *; yine de çalışır std::string.

İşte C ++ 11 lambdas kullanan ve dizeleri ayırmak for_eachiçin a sağlamaya izin veren başka bir çözüm separator:

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

Kullanım:

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

En azından bilgisayarımda hızlı bir testten sonra iyi ölçekleniyor (doğrusal olarak); İşte yazdığım hızlı bir test:

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

Sonuçlar (milisaniye):

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

3

Belki de "Streamer" çözümümün bunu gerçekten tek satırda yapmasını seviyorsunuz:

#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

İşte tek satırlık çözüm:

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

Biraz çirkin olmasına rağmen, bence C ++ 'da kediniz kadar temiz.

İlk argümanı a'ya atıyoruz std::stringve ardından sol operandının her zaman a operator+olmasını sağlamak için (soldan sağa) değerlendirme sırasını kullanıyoruz . Bu şekilde, soldaki ile sağdaki işleneni birleştiririz ve bir başkasını döndürerek efekti basamaklandırırız.std::stringstd::stringconst char *std::string

Not: Orada da dahil olmak üzere sağ işlenen için birkaç seçenek vardır const char *, std::stringve char.

Sihirli sayının 13 veya 6227020800 olup olmadığına karar vermek size kalmış.


Ah, unutuyorsun @Apollys, evrensel sihirli sayı 42'dir.: D
Bay Zeus

Cevabınıza "İşte tek satırlık çözüm" ile başlamak tuhaf , sanki tek satırlık bir tek sizmişsiniz (ki siz değilsiniz; birkaç, neredeyse on yıllık cevaplar zaten tek satırlık cevaplar sağladı). Diğer cevaplar bundan açıkça bahsetmiyor çünkü sorunun tam da öncülü tek satırlıktı , "Birden çok C ++ dizgisini bir satırda nasıl birleştirebilirim ? " . Gerçekten küstah tonunuzu biraz azaltmalısınız. Ayrıca, performans testleri ve diğer "temel bilgiler" de yapıyorsunuz, yine de her çıktıdan sonra çıktı tamponunuzu
yıkıyorsunuz


1

Kullanmaya istekli c++11iseniz, kullanıcı tanımlı dize değişmezlerini kullanabilir ve bir std::stringnesne ve diğer nesneler için artı işlecini aşırı yükleyen iki işlev şablonu tanımlayabilirsiniz . Tek tuzak, artı operatörlerini aşırı yüklememek std::string, aksi takdirde derleyici hangi operatörü kullanacağını bilemez. Bu şablonu kullanarak bunu yapabilirsiniz std::enable_ifdan type_traits. Bundan sonra dizeler Java veya C # 'teki gibi davranır. Ayrıntılar için örnek uygulamama bakın.

Ana kod

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

C_sharp_strings.hpp dosyası

Bu dizelere sahip olmak istediğiniz her yere bu başlık dosyasını ekleyin.

#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

Bunun gibi bir şey benim için çalışıyor

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

Yukarıdaki çözümlere dayanarak, hayatı kolaylaştırmak için projem için bir sınıf var_string yaptım. Örnekler:

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

Sınıfın kendisi:

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

Hala C ++ 'da daha iyi bir şey olup olmayacağını mı merak ediyorsunuz?


1

C11'de:

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

bu, aşağıdaki gibi işlev çağrısı oluşturmanıza izin verir:

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

yazdırılacak: mesaj numarası: 10


0

ayrıca dize sınıfını "genişletebilir" ve tercih ettiğiniz operatörü seçebilirsiniz (<<, &, |, vb ...)

Akışlarla herhangi bir çakışma olmadığını göstermek için << operatörünü kullanan kod aşağıdadır

not: s1.reserve (30) 'un açıklamasını kaldırırsanız, yalnızca 3 yeni () operatör isteği vardır (s1 için 1, s2 için 1, rezerv için 1; maalesef kurucu zamanında rezerve edemezsiniz); rezerv olmadan, s1 büyüdükçe daha fazla bellek istemelidir, bu nedenle derleyici uygulamanızın büyüme faktörüne bağlıdır (benimki bu örnekte 1.5, 5 new () çağrı gibi görünüyor)

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

Lambda işlevi kullanan basit bir önişlemci makrosu ile dizgi akışı güzel görünüyor:

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

ve sonra

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

-1

Bu benim için çalışıyor:

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

Çıktı:

c:\example\myfile.txt

12
Bir yavru kedi, birisi kod koruyuculardan veya sabitlerden daha karmaşık bir şey için makro kullandığında ağlar: P
Rui Marques

1
Mutsuz yavru kedilerin yanında: Her argüman için gerekli olmayan bir string nesnesi oluşturulur.
SebastianK

2
makro kullanmak kesinlikle kötü bir çözüm olduğundan olumsuz
oy verildi

bu beni C için bile dehşet içinde ürkütürdü, ama C ++ 'da şeytani. @RuiMarques: hangi durumlarda makrolar sabitler için a'dan daha iyidir constveya (sıfır depolama bir gereklilikse) an enumolur?
underscore_d

@underscore_d ilginç soru ama cevabım yok. Belki cevap hiçbiri değildir.
Rui Marques

-1

+ = 'Den kaçınmaya çalıştınız mı? bunun yerine var = var + ... kullanın benim için çalıştı.

#include <iostream.h> // for string

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

C ++ borland builder 6 kullanıyorum ve benim için iyi çalışıyor. bu başlıkları eklemeyi unutmayın#include <iostream.h> // string #include <system.hpp> // ansiString
vincent thorpe

+ = bu durum için aşırı yüklenmemiş, sayıları
eklemiş olduğunuzu
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.