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

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

上面的代码产生一个错误。

484423 次浏览
s += "Hello world, " + "nice to see you, " + "or not.";

这些字符数组文字不是 C + + std: : 字符串-您需要转换它们:

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

要转换 int (或任何其他可流式类型) ,可以使用 Bostlexical _ cast 或提供自己的函数:

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

你现在可以这样说:

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

格式

字符串

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


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

看看 Herb Sutter 的这篇文章: 庄园农场的弦乐版式

您必须为希望集中到字符串中的每个数据类型定义运算符 + () ,但是由于运算符 < < 是为大多数类型定义的,因此应该使用 std: : stringstream。

该死,比我早了50秒。

你的代码可以写成 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 可以写成”: 这只适用于字符串文字。连接由编译器完成。

这对我有用:

#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

为了提供一种更加单行的解决方案: 可以实现一个函数 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();
}

使用 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";

在这方面您可以使用这个头: https://github.com/theypsilon/concat

using namespace concat;


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

在引擎盖下,您将使用一个 std: : ostringstream。

5年来没有人提到过 .append

#include <string>


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

如果你写出 +=,它看起来几乎和 C # 一样

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

如果您愿意使用 c++11,您可以利用 用户定义的字符串文字并定义两个函数模板,它们为 std::string对象和任何其他对象重载加运算符。唯一的缺陷是不能重载 std::string的正运算符,否则编译器不知道使用哪个运算符。您可以通过使用来自 type_traits的模板 std::enable_if来实现这一点。在那之后,字符串的行为就像 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 _ string. 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

正如其他人所说,OP 代码的主要问题是操作符 +不连接 const char *; 但是它可以与 std::string一起工作。

下面是另一个使用 C + + 11 lambdas 和 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

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

下面是使用运算符 < < 来显示与流没有冲突的代码

注意: 如果你取消注释 s1.reserve (30) ,那么只有3个 new ()操作符请求(1个代表 s1,1个代表 s2,1个代表 reserve; 不幸的是,你不能在构造函数时保留) ; 没有 reserve,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;
}

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

真正的问题是在 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)

(现场演示。)

或者,如果您坚持使用字符串字面值的 +(正如 下划线已经建议的那样) :

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.";
auto s = string("one").append("two").append("three")

我觉得这样挺好

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

基于以上的解决方案,我为我的项目创建了一个 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 + + 会不会有更好的东西?

在 c11:

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

这样就可以像下面这样创建函数调用:

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

邮件编号: 10

在 C + + 20中,你可以做到:

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

在那之前,你可以对 { fmt }库做同样的事情:

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

免责声明 : 我是{ fmt }的作者。

下面是一个简单的解决方案:

#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取决于你。

你有没有尽量避免使用 + = ? 而是使用 var = var + ..。 对我很有效。

#include <iostream.h> // for string


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

Stringstream 带有一个使用 lambda 函数的简单预处理器宏,看起来不错:

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

然后

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