如何在一行上连接多个C++字符串?

191

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

以上代码会产生一个错误。


7
如前所述,这并不是因为“它不支持使用+运算符添加多个字符串/变量”,而是因为您正在尝试将char *指针相加。这就是导致错误的原因 - 因为对指针求和是没有意义的。如下所述,将至少第一个操作数转换为std::string,就不会出现错误了。 - underscore_d
1
产生了哪个错误? - Wolf
可能是如何连接std :: string和int?的重复问题。 - vitaut
25个回答

3
这里是一行代码的解决方案:
#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+从左到右的求值顺序,确保其操作数始终为 std::string。通过这种方式,我们将左侧的 std::string 与右侧的 const char * 操作数连接起来,并返回另一个 std::string,实现级联效果。
注意:右操作数有几个选项,包括 const char *std::stringchar
你可以决定这个魔法数字是13还是6227020800。

啊,你忘了,@Apollys,宇宙的魔法数字是42。 :D - Mr.Zeus
1
以“这里是一行解决方案”开始你的回答很奇怪,好像你是唯一一个有一行代码的人(其实不是;几乎十年前就已经提供了一行代码的答案)。其他答案没有明确提到它,因为一行代码是问题的前提,即“如何在一行上连接多个C++字符串?”。你应该降低你的傲慢语气。此外,你正在进行性能测试和其他“leet stuff”,但每次输出后都清空输出缓冲区... - Sebastian Mach

3

正如其他人所说,OP代码的主要问题在于运算符+不能连接const char *; 但是它可以使用std::string

这里有另一种解决方案,它使用C++11 lambda和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
如果您写出 += ,它看起来几乎与C#相同。
string s("Some initial data. "); int i = 5;
s = s + "Hello world, " + "nice to see you, " + to_string(i) + "\n";

3

如果您想将任何数据类型连接到字符串中,您需要为每种数据类型定义operator+(),但是由于大多数类型已经定义了operator<<,因此建议使用std::stringstream。

糟糕,被50秒打败了...


1
你实际上不能在内置类型(如char和int)上定义新的运算符。 - Tyler McHenry
1
@TylerMcHenry 我并不推荐在这种情况下这样写,但你可以这么做:std::string operator+(std::string s, int i){ return s+std::to_string(i); } - Eponymous

2
也许你会喜欢我的“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

基于上述解决方案,我为我的项目制作了一个名为 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。

1
如果您愿意使用,则可以利用用户定义的字符串字面量并定义两个函数模板,为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
你可以使用以下标题来进行相关操作:https://github.com/theypsilon/concat
using namespace concat;

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

在底层,您将使用std :: ostringstream。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接