Как объединить несколько строк 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-й операнд в a std::string, и ошибки нет вообще.
underscore_d

Какая ошибка возникла?
Волк

Ответы:


240
#include <sstream>
#include <string>

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

Взгляните на эту статью Гуру Недели от Херба Саттера: Строковые форматеры усадебной фермы


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

42
ss << "Ого, конкатенация строк в C ++ впечатляет" << "или нет."
Joaerl

4
Просто чтобы назвать другой способ: использование нескольких append: string 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();в значительной степени однострочный
Kotauskas

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 ОП объявляет 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 :: strings - их нужно преобразовать:

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 ("Hello world,") + "приятно видеть вас," + "или нет.";
Ферруччо,

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": / Это ошибка?
Привет, Ангел,

@ Hi-Angel К сожалению, нет, вместо этого вы можете #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
Патрисио Росси

пользовательские литералы также начиная с 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();
}

не станет ли это раздутием времени компиляции, если в большой кодовой базе есть несколько различных комбинаций.
Шиталь Шах

1
@ShitalShah - это не что иное, как ручная запись этих вещей, так как эти вспомогательные функции все равно будут встроены.
underscore_d

13

В C ++ 20 вы сможете делать:

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

До этого вы могли бы сделать то же самое с библиотекой {fmt} :

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

Отказ от ответственности : я автор {fmt}.


7

повышение :: формат

или 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)

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

или, если вы настаиваете на использовании +строковых литералов for (как уже предложено underscore_d ):

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

Другое решение объединяет строку и a const char*для каждого этапа объединения

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

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

Конечно, @BartMensfort sprintf- это вариант, но есть также std :: stringstream, который предотвращает проблемы с буферами меньшего размера .
Волк


3

Вам нужно было бы определить operator + () для каждого типа данных, который вы хотите связать со строкой, однако, поскольку 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и позволяет предоставлять 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

Может быть, вам нравится мое решение "Streamer", чтобы сделать это в одну строку:

#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объекта и любого другого объекта. Единственный подводный камень - не перегружать операторы plus 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

В с11:

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 запроса оператора new () (1 для s1, 1 для s2, 1 для резерва; к сожалению, вы не можете зарезервировать во время конструктора); без резерва, s1 должен запрашивать больше памяти по мере роста, так что это зависит от фактора роста реализации вашего компилятора (в нашем примере это, кажется, 1,5, 5 вызовов 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

Stringstream с простым макросом препроцессора с использованием лямбда-функции выглядит неплохо:

#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
Руи Маркиз

1
Рядом с несчастными котятами: для каждого аргумента создается строковый объект, который не нужен.
SebastianK

2
недооценено, поскольку использование макросов - определенно плохое решение
dhaumann

это заставило бы меня вздрогнуть от ужаса даже для C, но в C ++ это дьявольское. @RuiMarques: в каких ситуациях макросы лучше для констант, чем a constили (если требуется нулевое хранилище), чем enumбыло бы?
underscore_d

@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.