Làm cách nào để nối nhiều chuỗi C ++ trên một dòng?


150

C # có một tính năng cú pháp trong đó bạn có thể nối nhiều loại dữ liệu với nhau trên 1 dòng.

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

Điều gì sẽ tương đương trong C ++? Theo như tôi có thể thấy, bạn phải làm tất cả trên các dòng riêng biệt vì nó không hỗ trợ nhiều chuỗi / biến với toán tử +. Điều này là ổn, nhưng không có vẻ gọn gàng.

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

Các mã trên tạo ra một lỗi.


4
Như đã giải thích ở nơi khác, điều này không phải vì "nó không hỗ trợ nhiều chuỗi / biến với toán tử +" - mà là vì bạn đang cố gắng thêm các char *con trỏ vào nhau. Đó là những gì tạo ra lỗi - bởi vì con trỏ tổng hợp là vô nghĩa. Như đã lưu ý dưới đây, biến ít nhất toán hạng 1 thành một std::stringvà không có lỗi nào cả.
gạch dưới

Lỗi nào được tạo ra?
Sói

Bản sao có thể có của Cách nối chuỗi std :: và int?
vitaut

Câu trả lời:


239
#include <sstream>
#include <string>

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

Hãy xem bài viết này của Guru Of The Week từ Herb Sutter: The String Formatters of Manor Farm


6
Hãy thử điều này:std::string s = static_cast<std::ostringstream&>(std::ostringstream().seekp(0) << "HelloWorld" << myInt << niceToSeeYouString).str();
Byzantian

41
ss << "Wow, nối chuỗi trong C ++ có ấn tượng" << "hay không."
tham gia

4
Chỉ cần đặt tên theo cách khác: sử dụng nhiều append: string s = string ("abc"). Append ("def"). Append (otherStrVar) .append (to_opes (123));
Patricio Rossi

1
std::stringstream ss; ss << "Hello, world, " << myInt << niceToSeeYouString; std::string s = ss.str();là khá nhiều một dòng
Kotauskas

74

Trong 5 năm không ai nhắc đến .append?

#include <string>

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

Bởi vì nó cồng kềnh so với việc chỉ thêm một văn bản trong một dòng.
Hi-Angel

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

16
@Jonny s.append("One").append(" expression");Có lẽ tôi nên chỉnh sửa bản gốc để sử dụng giá trị trả về theo cách này?
Eponymous

5
@ SilverMöls OP tuyên bố strên một dòng khác trong mã C # tương đương và trong mã C ++ không biên dịch của mình. C ++ mong muốn của anh ấy s += "Hello world, " + "nice to see you, " + "or not.";có thể được viếts.append("Hello world, ").append("nice to see you, ").append("or not.");
Eponymous

4
Một lợi thế lớn của appendnó là nó cũng hoạt động khi các chuỗi chứa các ký tự NUL.
John S.

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

Những mảng ký tự đó không phải là C ++ std :: chuỗi - bạn cần chuyển đổi chúng:

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

Để chuyển đổi ints (hoặc bất kỳ loại có thể phát trực tuyến nào khác), bạn có thể sử dụng boost lexical_cast hoặc cung cấp chức năng của riêng bạn:

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

Bây giờ bạn có thể nói những điều như:

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

16
Bạn chỉ cần chuyển đổi rõ ràng chuỗi đầu tiên: s + = chuỗi ("Xin chào thế giới") + "rất vui được gặp bạn," + "hoặc không.";
Ferruccio

8
Vâng, nhưng tôi không thể giải thích tại sao!

1
boost :: lexical_cast - đẹp và tương tự trên chức năng Str của bạn :)
bayda

2
Các phép nối được thực hiện ở bên phải của hàm tạo string("Hello world")được thực hiện thông qua operator+()định nghĩa trong lớp string. Nếu không có stringđối tượng trong biểu thức, phép nối trở thành một tổng của các con trỏ char char*.
davide

41

Mã của bạn có thể được viết là 1 ,

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

... nhưng tôi nghi ngờ đó là những gì bạn đang tìm kiếm. Trong trường hợp của bạn, có lẽ bạn đang tìm kiếm luồng:

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

1 " có thể được viết là ": Điều này chỉ hoạt động cho chuỗi ký tự. Việc ghép được thực hiện bởi trình biên dịch.


11
Ví dụ đầu tiên của bạn rất đáng được đề cập, nhưng xin vui lòng đề cập rằng nó chỉ hoạt động đối với các chuỗi ký tự "nối" (trình biên dịch thực hiện chính phép nối).
j_random_hacker

Ví dụ đầu tiên gây ra lỗi cho tôi nếu một chuỗi được khai báo trước đó là vd const char smthg[] = "smthg": / Đây có phải là lỗi không?
Hi-Angel

@ Hi-Angel Thật không may, thay vào đó, bạn có thể #definexâu chuỗi chuỗi này, mặc dù điều này mang đến những vấn đề riêng.
cz

27

Sử dụng C ++ 14 người dùng xác định bằng chữ và std::to_stringmã trở nên dễ dàng hơn.

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;

Lưu ý rằng chuỗi ký tự chuỗi có thể được thực hiện tại thời gian biên dịch. Chỉ cần loại bỏ +.

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

2
Kể từ C ++ 11, bạn có thể sử dụng std :: to_opes
Patricio Rossi

người dùng định nghĩa bằng chữ cũng kể từ C ++ 11 <> . Tôi đã chỉnh sửa.
Chồng Daniel

@StackDanny Thay đổi là sai. Khi tôi nói "C ++ 14", tôi không nói đến std::literals::string_literalskhái niệm UDL.
Rapptz

16

Để cung cấp một giải pháp nhiều hơn một dòng: Một hàm concatcó thể được thực hiện để giảm giải pháp dựa trên chuỗi "cổ điển" xuống một câu lệnh . Nó được dựa trên các mẫu matrixdic và chuyển tiếp hoàn hảo.


Sử dụng:

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

Thực hiệ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();
}

điều này sẽ không trở thành sự phình to thời gian nếu có một số kết hợp khác nhau trong cơ sở mã lớn.
Shital Shah

1
@ShitalShah không chỉ đơn thuần là viết nội dung đó theo cách thủ công, vì các hàm trợ giúp này sẽ chỉ được đặt nội tuyến.
gạch dưới

13

Trong C ++ 20 bạn sẽ có thể làm:

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

Cho đến lúc đó bạn có thể làm tương tự với thư viện {fmt} :

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

Tuyên bố miễn trừ trách nhiệm : Tôi là tác giả của {fmt}.


7

tăng :: định dạng

hoặc std :: chuỗi dòng

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

6

Các vấn đề thực tế là concatenating xâu với +thất bại trong C ++:

string s;
s += "Hello world, " + "nice to see you, " + "or not.";
Các mã trên tạo ra một lỗi.

Trong C ++ (cũng bằng C), bạn ghép nối các chuỗi ký tự bằng cách chỉ đặt chúng ngay cạnh nhau:

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

Điều này có ý nghĩa, nếu bạn tạo mã theo macro:

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

... một macro đơn giản có thể được sử dụng như thế này

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

( bản demo trực tiếp ... )

hoặc, nếu bạn khăng khăng sử dụng +chuỗi ký tự cho chuỗi (như đã được đề xuất bởi underscore_d ):

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

Một giải pháp khác kết hợp một chuỗi và một const char*cho mỗi bước nối

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

Tôi cũng sử dụng kỹ thuật này rất nhiều, nhưng nếu một hoặc nhiều biến là int / chuỗi thì sao? .eg chuỗi s = "abc" "def" (int) y "ghi" (std :: chuỗi) z "1234"; sau đó, sprintf vẫn là giải pháp tốt nhất.
Bart Mensfort

@BartMensfort tất nhiên sprintflà một tùy chọn, nhưng cũng có std :: chuỗi dòng giúp ngăn chặn các vấn đề với bộ đệm quá nhỏ.
Sói


3

Bạn sẽ phải xác định toán tử + () cho mọi loại dữ liệu bạn muốn nối với chuỗi, tuy nhiên vì toán tử << được xác định cho hầu hết các loại, bạn nên sử dụng std :: stringstream.

Chết tiệt, đánh bại 50 giây ...


1
Bạn thực sự không thể xác định các toán tử mới trên các loại tích hợp như char và int.
Tyler McHenry

1
@TylerMcHenry Không phải tôi muốn giới thiệu nó trong trường hợp này, nhưng bạn chắc chắn có thể:std::string operator+(std::string s, int i){ return s+std::to_string(i); }
Eponymous

3

Nếu bạn viết ra +=, nó trông gần giống như C #

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

3

Như những người khác đã nói, vấn đề chính với mã OP là toán tử +không nối được const char *; Nó làm việc với std::string, mặc dù.

Đây là một giải pháp khác sử dụng C ++ 11 lambdas for_eachvà cho phép cung cấp một separatorchuỗi để tách các chuỗi:

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

Sử dụng:

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

Nó dường như mở rộng tốt (tuyến tính), ít nhất là sau khi thử nghiệm nhanh trên máy tính của tôi; đây là một bài kiểm tra nhanh tôi đã viết:

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

Kết quả (mili giây):

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

3

Có thể bạn thích giải pháp "Streamer" của tôi để thực sự làm điều đó trong một dòng:

#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

Đây là giải pháp một lớp lót:

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

Mặc dù nó hơi xấu xí một chút, nhưng tôi nghĩ nó sạch sẽ như con mèo của bạn trong C ++.

Chúng tôi đang truyền đối số đầu tiên cho a std::stringvà sau đó sử dụng thứ tự đánh giá (trái sang phải) operator+để đảm bảo rằng toán hạng bên trái của nó luôn luôn là a std::string. Theo cách này, chúng ta ghép nối std::stringbên trái với const char *toán hạng bên phải và trả về một cái khác std::string, xếp tầng hiệu ứng.

Lưu ý: có một vài lựa chọn cho các toán hạng phải, bao gồm const char *, std::stringchar.

Tùy thuộc vào bạn để quyết định xem số ma thuật là 13 hay 6227020800.


À, bạn quên rồi, @Apollys, số ma thuật phổ quát là 42 .: D
Mr.Zeus


1

Nếu bạn sẵn sàng sử dụng, c++11bạn có thể sử dụng chuỗi ký tự do người dùng định nghĩa và xác định hai mẫu hàm làm quá tải toán tử cộng cho một std::stringđối tượng và bất kỳ đối tượng nào khác. Cạm bẫy duy nhất là không làm quá tải các toán tử cộng std::string, nếu không trình biên dịch không biết sử dụng toán tử nào. Bạn có thể làm điều này bằng cách sử dụng các mẫu std::enable_iftừ type_traits. Sau đó, các chuỗi hoạt động giống như trong Java hoặc C #. Xem ví dụ thực hiện của tôi để biết chi tiết.

Mã chính

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

Tệp c_sharp_strings.hpp

Bao gồm tệp tiêu đề này ở tất cả các nơi bạn muốn có các chuỗi này.

#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

Một cái gì đó như thế này làm việc cho tôi

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

Dựa trên các giải pháp trên, tôi đã tạo một var_ chuỗi lớp cho dự án của mình để làm cho cuộc sống dễ dàng. Ví dụ:

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

Bản thân lớp học:

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

Vẫn đang tự hỏi liệu sẽ có một cái gì đó tốt hơn trong C ++?


1

Trong c11:

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

điều này cho phép bạn tạo chức năng gọi như thế này:

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

sẽ in: số tin nhắn: 10


0

bạn cũng có thể "mở rộng" lớp chuỗi và chọn toán tử bạn thích (<<, &, |, v.v ...)

Đây là mã sử dụng toán tử << để cho thấy không có xung đột với các luồng

lưu ý: nếu bạn không ghi chú s1.reserve (30), chỉ có 3 yêu cầu toán tử () mới (1 cho s1, 1 cho s2, 1 cho dự trữ; bạn không thể dự trữ vào thời gian của nhà xây dựng); không có dự trữ, s1 phải yêu cầu thêm bộ nhớ khi nó phát triển, do đó, nó phụ thuộc vào yếu tố tăng trưởng triển khai trình biên dịch của bạn (dường như là 1,5, 5 lệnh gọi () mới trong ví dụ này)

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

Chuỗi dòng với macro tiền xử lý đơn giản sử dụng hàm lambda có vẻ hay:

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

và sau đó

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

-1

Điều này làm việc cho tôi:

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

Đầu ra:

c:\example\myfile.txt

12
Một con mèo con khóc mỗi khi ai đó sử dụng macro cho một thứ gì đó phức tạp hơn những người bảo vệ mã hoặc hằng số: P
Rui Marques

1
Bên cạnh những chú mèo con không vui: Đối với mỗi đối số, một đối tượng chuỗi được tạo ra là không cần thiết.
SebastianK

2
bị từ
chối

điều này sẽ khiến tôi nao núng trong nỗi kinh hoàng ngay cả đối với C, nhưng trong C ++, đó là sự liều lĩnh. @RuiMarques: trong trường hợp nào các macro tốt hơn cho các hằng số hơn một consthoặc (nếu không có dung lượng lưu trữ là một yêu cầu) enumthì sẽ là gì?
gạch dưới

@underscore_d câu hỏi thú vị nhưng tôi không có câu trả lời cho nó. Có lẽ câu trả lời là không.
Rui Marques

-1

Bạn đã cố gắng tránh + =? thay vì sử dụng var = var + ... nó làm việc cho tôi.

#include <iostream.h> // for string

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

Tôi sử dụng C ++ borland builder 6, và nó hoạt động tốt với tôi. đừng quên bao gồm các tiêu đề này#include <iostream.h> // string #include <system.hpp> // ansiString
vincent thorpe

+ = không bị quá tải trong trường hợp này, dường như nghĩ rằng bạn đã thêm số và không nối chuỗi
vincent thorpe
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.