2009-03-19 2 views
100

C# имеет функцию синтаксиса, в которой вы можете объединить многие типы данных вместе на 1 строке.Как объединить несколько строк 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."; 

Приведенный выше код вызывает ошибку.

+1

Как объяснено в другом месте, это не потому, что " он не поддерживает несколько строк/переменных с помощью оператора +), а скорее потому, что вы пытаетесь добавить указатели 'char *' друг к другу. Вот что порождает ошибку - потому что указатели суммирования бессмысленны. Как отмечено ниже, сделайте по крайней мере 1-й операнд в 'std :: string', и вообще нет ошибки. –

+0

Какая ошибка была произведена? – Wolf

ответ

174
#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

+78

Возможно, я что-то пропустил, но это не в одной строке ... –

+3

Попробуйте следующее: 'std :: string s = static_cast (std :: ostringstream(). Seekp (0) <<" HelloWorld "<< myInt << niceToSeeYouString) .str();' – CaffeineAddict

+7

@Lohoris: это конкатенация, которая происходит в одной строке ... –

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

Этих литералы массив символов не являются C++ STD :: строки - вы определены, чтобы преобразовать их:

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 = "The meaning is " + Str(42); 
+10

Вам нужно только явно преобразовать первый: s + = string («Hello world») + «приятно видеть вас», «+» или нет. »; – Ferruccio

+5

Да, но я не мог объяснить, почему! – 2009-03-19 16:31:20

+1

boost :: lexical_cast - приятный и похожий на вашу функцию Str :) – bayda

7

подталкивание :: Формат

или станд :: stringstream

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

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

Блин, избили 50 секунд ...

+1

Вы не можете определить новые операторы для встроенных типов, таких как char и int. –

+0

@TylerMcHenry Не то, чтобы я рекомендовал его в этом случае, но вы, безусловно, можете: 'std :: string operator + (std :: string s, int i) {return s + std :: to_string (i); } ' – Eponymous

33

Ваш код может быть записан в виде ,

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

«можно записать в виде»: Это работает только для строковых литералов. Конкатенация выполняется компилятором.

+7

Ваш 1-й пример заслуживает упоминания, но, пожалуйста, также упоминайте, что он работает только для« конкатенирования »литералов (компилятор сам выполняет конкатенацию). –

+0

Первый пример вызвал ошибку для меня, если ранее была объявлена ​​строка.'const char smthg [] =" smthg "':/Это ошибка? –

-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 
+9

Котенок плачет каждый раз, когда кто-то использует макросы для чего-то более сложного, чем защитные коды или константы: P –

+1

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

+1

downvoted, так как использование макросов, безусловно, плохое решение – dhaumann

12

Чтобы предложить решение, которое является еще одной строки иш: Функция 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(); 
} 
+0

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

15

Использование 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"; 
+0

С C++ 11 вы можете использовать std :: to_string –

0

Вы можете использовать этот заголовок для этой связи: https://github.com/theypsilon/concat

using namespace concat; 

assert(concat(1,2,3,4,5) == "12345"); 

Под капотом вы будете использовать зЬй :: ostringstream.

34

Через 5 лет никто не упомянул .append?

#include <string> 

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

Потому что это громоздко по сравнению с просто добавлением текста в одну строку. –

+2

's.append (« Один »); s.append ("line"); ' – Jonny

+9

@Jonny' s.append («One»). append («expression»); «Может быть, мне нужно отредактировать оригинал, чтобы использовать возвращаемое значение таким образом? – Eponymous

1

Если вы выписывать +=, он выглядит почти так же, как C#

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

Если вы готовы использовать c++11 вы можете использовать user-defined string literals и определить две шаблоны функций, которые перегружают оператор плюс для объект 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 
0

Как другие говорили, главная проблема с кодом 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 
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; 
} 
1

Может быть, вам нравится мой " 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; 
} 
1

актуальная проблема, что конкатенации строковых литералов + терпит неудачу в 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) 

(live demo ...)

или, если вы настаиваете на использовании + для строковых литералов (как уже было предложено 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."; 
+0

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

+0

@BartMensfort Конечно, 'sprintf' является опцией, но также существует [std :: stringstream] (http://en.cppreference.com/w/cpp/io/basic_stringstream), который предотвращает проблемы с низкорослыми буферами. – Wolf

0

Что-то вроде это работает для меня

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); 
0

Исходя из вышеуказанных решений, которые я сделал класс 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++?

0

В c11:

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

это позволит вам создать вызов функции, как это:

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

напечатает: Номер сообщения: 10