Як з'єднати кілька рядків C ++ в один рядок?


150

C # має синтаксичну функцію, за якою ви можете об'єднати багато типів даних у одному рядку.

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

Що було б еквівалентом у C ++? Наскільки я бачу, вам доведеться робити це все в окремих рядках, оскільки він не підтримує кілька рядків / змінних з оператором +. Це нормально, але не виглядає так акуратно.

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

Вищевказаний код створює помилку.


4
Як пояснено в іншому місці, це не тому, що "він не підтримує кілька рядків / змінних з оператором +", а скоріше тому, що ви намагаєтесь додавати char *покажчики один одному. Саме це породжує помилку - адже підсумовувати покажчики є безглуздим. Як зазначалося нижче, перетворіть принаймні 1-й операнд у std::string, і помилки взагалі немає.
підкреслюй_

Яка помилка виникла?
Вовк

Можливий дублікат Як об'єднати std :: string та int?
vitaut

Відповіді:


240
#include <sstream>
#include <string>

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

Погляньте на цю статтю Гуру Тижня з Herb Sutter: String Formatters of Manor Farm


6
Спробуйте так:std::string s = static_cast<std::ostringstream&>(std::ostringstream().seekp(0) << "HelloWorld" << myInt << niceToSeeYouString).str();
Візантійці

42
ss << "Нічого, конкатенація рядків у C ++ вражає" << "чи ні."
joaerl

4
Просто назвіть інший спосіб: використовуючи множинне додавання: рядок s = string ("abc"). Append ("def"). Append (otherStrVar) .append (to_string (123));
Патрісіо Россі

1
std::stringstream ss; ss << "Hello, world, " << myInt << niceToSeeYouString; std::string s = ss.str();майже однолінійний
Котаускас

74

За 5 років ніхто не згадав .append?

#include <string>

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

Тому що це громіздко порівняно з просто додаванням тексту в один рядок.
Привіт-Ангел

11
s.append("One"); s.append(" line");
Джонні

16
@Jonny s.append("One").append(" expression");Можливо, я повинен редагувати оригінал, щоб таким чином використовувати значення повернення?
однойменний

5
@ SilverMöls OP заявляє sна інший рядок у еквівалентному коді C # та у своєму некомпілюючому коді C ++. Його бажаний C ++ - це те, s += "Hello world, " + "nice to see you, " + "or not.";що можна записатиs.append("Hello world, ").append("nice to see you, ").append("or not.");
однойменний октябрь

4
Основною перевагою appendє те, що він також працює, коли рядки містять символи NUL.
Джон С.

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

Ці літеральні масиви символів не є C ++ std :: string - вам потрібно їх перетворити:

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

Для перетворення вкладишів (або будь-якого іншого типу, що передається в реальному часі) ви можете скористатися збільшити lexical_cast або надати свою функцію:

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

Тепер ви можете сказати такі речі, як:

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

16
Вам потрібно лише явно перетворити перший: s + = string ("Привіт, світ") + "приємно бачити тебе" + "чи ні.";
Ferruccio

8
Так, але я не міг зіткнутися з поясненням чому!

1
boost :: lexical_cast - приємно і схоже на функції Str: :)
bayda

2
Зв'язки, проведені праворуч від конструктора string("Hello world"), виконуються за допомогою operator+()визначених у класі string. Якщо stringв виразі немає об'єкта, конкатенація стає простою сумою покажчиків char*.
Давиде

41

Ваш код можна записати як 1 ,

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

... але я сумніваюся, що це ви шукаєте. У вашому випадку ви, ймовірно, шукаєте потоки:

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

1 " можна записати як ": Це працює лише для рядкових літералів. З'єднання здійснюється компілятором.


11
Ваш перший приклад варто згадати, але будь ласка, також зауважте, що він працює лише для "об'єднання" буквальних рядків (компілятор виконує конкатенацію сам).
j_random_hacker

Перший приклад викликав помилку для мене, якщо рядок раніше був оголошений як, наприклад const char smthg[] = "smthg": / Це помилка?
Привіт-Ангел

@ Привіт-Ангел На жаль, натомість, ви можете #defineнабрати рядки, щоб обійти це, хоча це приносить свої проблеми.
cz

27

Використовуючи C ++ 14 визначені користувачем літерали, і std::to_stringкод стає простішим.

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;

Зауважте, що об'єднання літеральних рядків може здійснюватися під час компіляції. Просто видаліть +.

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

2
Оскільки C ++ 11 ви можете використовувати std :: to_string
Patricio Rossi

визначені користувачем літерали також з C ++ 11 <> . Я редагував.
Стек Данні

@StackDanny Зміна неправильна. Коли я кажу "C ++ 14", я посилаюсь на std::literals::string_literalsпоняття, а не на UDL.
Раппц

16

Щоб запропонувати рішення, яке є більш однорядковим: concatможе бути реалізована функція, щоб зменшити "класичне" рішення, що базується на потоці, до одного оператора . Він заснований на різноманітних шаблонах та ідеальній переадресації.


Використання:

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

Впровадження:

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

Чи не стане це час компіляції, якщо у великій базі коду є кілька різних комбінацій.
Shital Shah

1
@ShitalShah не більше, ніж просто писати цей матеріал вбудовано вручну, оскільки ці допоміжні функції все одно просто вбудуються.
підкреслюй_d

13

У мові C ++ 20 ви зможете:

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

До цього ви можете зробити те ж саме з бібліотекою {fmt} :

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

Відмова : Я автор {fmt}.


7

boost :: формат

або std :: stringstream

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

6

Актуальна проблема в тому , що конкатенації строкових літералів +зазнає невдачі в C ++:

string s;
s += "Hello world, " + "nice to see you, " + "or not.";
Вищевказаний код створює помилку.

У C ++ (також на C) ви об'єднуєте рядкові літерали, просто розміщуючи їх прямо поруч:

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

Це має сенс, якщо ви генеруєте код у макросах:

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

... простий макрос, який можна використовувати так

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

( жива демонстрація ... )

або, якщо ви наполягаєте на використанні +для рядкових літералів (як уже запропоновано underscore_d ):

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

Інше рішення поєднує рядок і а const char*для кожного кроку з'єднання

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

Я також дуже часто використовую цю техніку, але що робити, якщо одна чи кілька змінних є int / string? .eg рядок s = "abc" "def" (int) y "ghi" (std :: string) z "1234"; значить, спринт все ще є найкращим із гірших рішень.
Барт Менсфорт

@BartMensfort звичайно sprintfє варіантом, але є також std :: stringstream, який запобігає проблемам з низькорозмірними буферами.
Вовк


3

Вам слід було б визначити operator + () для кожного типу даних, який ви хочете присвоїти рядку, але оскільки оператор << визначений для більшості типів, ви повинні використовувати std :: stringstream.

Чорт, побили 50 секунд ...


1
Насправді ви не можете визначити нових операторів для таких вбудованих типів, як char та int.
Тайлер Макенрі

1
@TylerMcHenry Не те, що я б рекомендував це в цьому випадку, але ви, безумовно, можете:std::string operator+(std::string s, int i){ return s+std::to_string(i); }
однойменний

3

Якщо ви виписуєте +=, це виглядає майже так само, як C #

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

3

Як говорили інші, головна проблема коду OP полягає в тому, що оператор +не з'єднується const char *; він працює std::string, хоча.

Ось ще одне рішення, яке використовує лямбди C ++ 11 for_eachі дозволяє надати a 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();
}

Використання:

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

Здається, він масштабується (лінійно), принаймні після швидкого тестування на моєму комп’ютері; ось короткий тест, який я написав:

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

Результати (мілісекунд):

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

3

Можливо, вам подобається моє рішення "Стример", щоб дійсно зробити це в один рядок:

#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

Ось однолінійне рішення:

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

Хоча це крихітно трохи потворно, я думаю, що це так само чисто, як ви потрапляєте в C ++.

Ми кидаємо перший аргумент на a, std::stringа потім використовуємо (зліва направо) порядок оцінки, operator+щоб переконатися, що його лівий операнд завжди є a std::string. Таким чином ми з'єднуємо std::stringліворуч з const char *операндом праворуч і повертаємо інший std::string, каскадуючи ефект.

Примітка: Є кілька варіантів для правого операнда, в тому числі const char *, std::stringі char.

Ви вирішуєте, чи буде магічне число 13 чи 6227020800.


А, ви забули, @Apollys, універсальне магічне число - 42.: D
Mr.Zeus


1

Якщо ви бажаєте використовувати, c++11ви можете використовувати визначені користувачем рядкові літерали та визначити два шаблони функцій, які перевантажують оператор плюс для std::stringоб'єкта та будь-якого іншого об'єкта. Єдина проблема - не перевантажувати плюсові оператори std::string, інакше компілятор не знає, який оператор використовувати. Це можна зробити за допомогою шаблону std::enable_ifз type_traits. Після цього рядки ведуть себе так само, як у Java або C #. Деталі див. У моєму прикладі реалізації.

Основний код

#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

Додайте цей заголовок у всі місця, де ви хочете мати ці рядки.

#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

Щось подібне працює для мене

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

Спираючись на вищезазначені рішення, я створив класову var_string для свого проекту, щоб полегшити життя. Приклади:

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

Сам клас:

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

Все ще цікаво, чи буде щось краще в C ++?


1

У c11:

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

це дозволяє створити функціональний виклик таким чином:

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

буде надруковано: номер повідомлення: 10


0

Ви також можете "розширити" клас рядків та вибрати оператора, який Ви бажаєте (<<, &, | тощо).

Ось код, використовуючи оператор <<, щоб показати, що немає конфлікту з потоками

Примітка: якщо ви відміняєте s1.reserve (30), є лише 3 нові запити () оператора (1 для s1, 1 для s2, 1 для резерву; на жаль, ви не можете забронювати на час конструктора); без резерву, s1 повинен запитувати більше пам’яті в міру зростання, тому це залежить від коефіцієнта росту вашої реалізації компілятора (моєму здається, 1,5, 5 нових () викликів у цьому прикладі)

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

Струмовий потік з простим макросом препроцесора за допомогою функції лямбда здається приємним:

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

і потім

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

-1

Це працює для мене:

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

Вихід:

c:\example\myfile.txt

12
Кошеня плаче кожного разу, коли хтось використовує макроси для чогось більш складного, ніж кодовий охоронець чи константи: P
Rui Marques

1
Поруч нещасних кошенят: для кожного аргументу створюється рядовий об’єкт, який не є необхідним.
СебастьянК

2
неприхильне, оскільки використання макросів, безумовно, є поганим рішенням
dhaumann

це змусить мене з жахом тремтіти навіть за С, але в С ++ - це дьявольське. @RuiMarques: в яких ситуаціях макроси краще для констант, ніж a const(якщо нульове зберігання є вимогою) an enum?
підкреслюй_

@underscore_d цікаве питання, але у мене немає відповіді на нього. Можливо, відповідь одна.
Руй Маркес

-1

Чи намагалися ви уникати + =? замість цього використовуйте var = var + ... це працювало для мене.

#include <iostream.h> // for string

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

Я використовую C ++ borland builder 6, і це прекрасно працює для мене. не забудьте включити цей заголовок#include <iostream.h> // string #include <system.hpp> // ansiString
Вінсент Торп

+ = не перевантажений для цього випадку, здається, ви думаєте, що ви додали цифри і не
з'єднали
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.