如何在一行上连接多个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 *彼此添加指针。这就是产生错误的原因-因为对指针求和是荒谬的。如下所述,至少将第一个操作数设为std::string,并且完全没有错误。
underscore_d 2015年

产生了哪个错误?

Answers:


239
#include <sstream>
#include <string>

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

看看Herb Sutter撰写的本周大师文章:庄园农场的弦乐格式化者


6
试试这个:std::string s = static_cast<std::ostringstream&>(std::ostringstream().seekp(0) << "HelloWorld" << myInt << niceToSeeYouString).str();
Byzantian 2013年

41
ss <<“哇,C ++中的字符串连接令人印象深刻” <<“或否。”
joaerl 2014年

4
只是说另一种方式:使用多个追加: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();几乎是单行
Kotauskas

74

在5年内没有人提及.append

#include <string>

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

与仅在一行中添加文本相比,这样做比较麻烦。
Hi-Angel

11
s.append("One"); s.append(" line");
强尼

16
@Jonny s.append("One").append(" expression");也许我应该编辑原始文件,以这种方式使用返回值?
同义的2015年

5
@SilverMölsOP 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.");
同义的,2015年

4
的主要优点append是,当字符串包含NUL字符时,它也可以工作。
John S.

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

要转换整数(或任何其他可流类型),可以使用boost 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,”)+“很高兴见到您,” +“或不。”;
Ferruccio

8
是的,但我无法解释原因!

1
boost :: lexical_cast-和您的Str函数类似:)
bayda

2
构造函数右侧的串联string("Hello world")是通过operator+()在类中定义的来执行的string。如果string表达式中没有对象,则串联仅是char指针的和char*
davide 2015年

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

@ 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
Patricio Rossi

自C ++ 11 <>以来,用户定义的文字也是如此。我编辑了。
丹尼·丹尼

@StackDanny更改错误。当我说“ C ++ 14”时,我指std::literals::string_literals的是UDL,而不是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只不过是手动内联编写这些东西,因为无论如何这些辅助功能都将内联。
underscore_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)

现场演示...

或者,如果您坚持使用+for字符串文字(如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 string s =“ abc”“ def”(int)y“ ghi”(std :: string)z“ 1234”; 然后,sprintf仍然是较差解决方案中最好的。
Bart Mensfort

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

也许您喜欢我的“ 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 ++一样干净。

我们将第一个参数强制转换为 std::string,然后使用(从左到右)求值顺序operator+来确保其操作数始终为a std::string。通过这种方式,我们std::string将左侧的const char *操作数与右侧的操作数连接起来,并返回另一个std::string,级联效果。

注:对于正确的操作,其中包括几个选项const char *std::stringchar

由您决定魔术数是13还是6227020800。


啊,你忘了,@ Apollys,通用幻数是
42。:D


1

如果愿意,c++11可以使用用户定义的字符串文字并定义两个函数模板,这些函数模板会使一个std::string对象和任何其他对象的plus运算符重载。唯一的陷阱是不要重载的plus运算符std::string,否则编译器不知道要使用哪个运算符。您可以通过使用模板做到这一点std::enable_iftype_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

您还可以“扩展”字符串类并选择您喜欢的运算符(<<,&,|等)。

这是使用operator <<的代码,显示与流没有冲突

注意:如果取消注释s1.reserve(30),则只有3个new()运算符请求(s1 1个,s2 1个,reserve 1个;不幸的是,您不能在构造函数时保留);没有保留,s1必须随着增长而请求更多的内存,因此这取决于您的编译器实现增长因子(在本示例中,mine似乎是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

具有使用lambda函数的简单前置宏的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
Rui Marques

1
在不幸的小猫旁边:对于每个参数,都创建了一个字符串对象,这是不必要的。
SebastianK

2
不推荐使用,因为使用宏绝对是一个不好的解决方案
dhaumann

即使是C语言,这也会让我感到恐惧,但是在C ++中,这简直是恶魔般的。@RuiMarques:在哪种情况下,宏比使用a const或(如果要求零存储)enum更好?
underscore_d 2015年

@underscore_d有趣的问题,但我没有答案。也许答案是否定的。
Rui Marques 2015年

-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
森特

+ =在这种情况下没有过载,似乎认为您在添加数字而不是连接字符串
森特·索普19/3/14
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.