如何在C++中添加多个字符串

14

据我所知,C++只允许将两个字符串相加,即: s = s1 + s2

但是如何将多个字符串拼接在一起呢?例如:

s = s1 + s2 + s3 + s4 + ... + sn

9
你为什么认为这个方法行不通? - bmargulies
1
你正在使用字符串类吗? - cpx
是的,我注意到它只在我使用字符串类时才起作用。 但我想在C++中做这样的事情:#define St "blah3" s = s1 + "blah1" + "blah2" + St - user188276
2
@tsubasa:嗯,你不能这样做。如果你在编译时知道两个字面量,只需将它们相邻放置即可使它们在预处理器中连接起来。例如,"asd" "123" 变成了 "asd123"。但是,运行时字符串的添加需要使用字符串类。 - GManNickG
同时记得 "blah1" + "blah2" 是不对的。它将会把指针值相加而非连接字符串。 - Billy ONeal
1
@Billy:嗯,它不会添加指针值本身。它会尝试,但编译失败。 - GManNickG
6个回答

15
如果你想要连接 std::string 类的字符串对象,那么这段代码应该可以工作。
string s1 = "string1";
string s2 = "string2";
string s3 = "string3";

string s = s1 + s2 + s3;

或者

string s = string("s1") + string("s2") + string("s3") ...

8
请记住,在C++03中使用operator+(std::basic_string<t>, std::basic_string<t>)会产生指数级的时间复杂度。与之相比,使用std::basic_string<t>::append成员函数只需要线性时间。 - Billy ONeal
我认为string s = string("s1") + string("s2") + string("s3") ... 是我正在寻找的东西。不知道它为什么有效呢? - user188276
2
因为当你使用string(x)时,你会在x上调用标准字符串类的构造函数。你创建了一个临时字符串对象,它可以参与运算符+的操作。(这有点像一种类型转换) - Billy ONeal
2
@tsubasa:只要确保第一个项目是“string”,那么string("s1") + "s2" + "s3"也可以工作。原因是普通的字符串常量如“s1”属于类型const char *,您不能将这些指针简单地相加。另一方面,string对象知道如何添加const char *以形成新的string。(另外:在这里没有使用指数时间,所以不用太担心) - sth
3
你确定它是指数时间吗?在我看来,它是O(N^2)。 - Steve Jessop
@Steve:是的,我的错,看一下我回答的评论。它不让我在这里编辑那个评论。二次时间复杂度也不是一个好选择。 - Billy ONeal

8
首先,你可以很好地完成+sn的操作。不过,假设你在C++03中使用std::basic_string字符串,它将需要二次时间(请参见注释)。
你可以使用std::basic_string::append与std::basic_string::reserve结合使用,在O(n)时间内连接你的字符串。
编辑:例如:
string a;
//either
a.append(s1).append(s2).append(s3);
//or
a.append("I'm a string!").append("I am another string!");

1
这不会花费指数时间,只是二次时间。append会更快,但通常仍然是二次时间,因为它需要不时重新分配内存。在大多数情况下,这两种方法都不会慢到能够被察觉到。 - sth
2
@BillyONeal:为什么会是指数级的?你提到的总和等于(0*N + N) + (1*N + N) + (2*N + N) + ... + ((K-1)*N + N),这相当于(1+2+...+(K-1)) * N + K*N=(K*(K-1)/2) * N + K*N=((K^2 + K) / 2) * N=O(K^2 * N)。因此,它在部分数量K方面是二次的,在每个部分中的字符数量N方面是线性的。 - sth
1
关于append():我好像错过了你提到的reserve()。如果与reserve()一起使用,它确实是线性的。 - sth
3
即使没有预留足够的空间,append 函数也会以线性摊销时间运行,这比平方级别的时间要快得多。 - fredoverflow
1
@BillyONeal 你可以说它是指数级的,其中所涉及的指数恰好为2(尽管这仍然不太好:P) - Steven Lu
显示剩余5条评论

5
s = s1 + s2 + s3 + .. + sn;

尽管可能会创建很多临时变量(一个好的优化编译器应该会有所帮助),但这段代码仍然可以工作,因为它将有效地被解释为:

string tmp1 = s1 + s2;
string tmp2 = tmp1 + s3;
string tmp3 = tmp2 + s4;
...
s = tmpn + sn;

一种保证不会创建临时变量的替代方法是:
s = s1;
s += s2;
s += s3;
...
s += sn;

你的例子并不完全相同,tmp1 必须进行复制构造。如果 tmp1 是 'string const&' 就会是相同的结果。尽管我必须承认 RVO 可能会消除这个复制。 - Martin York

3

std::ostringstream 是专为此而构建的,可以看这里的示例(点击此处)。使用起来很简单:

std::ostringstream out;
out << "a" << "b" << "c" << .... << "z";
std::string str( out.str());

1
我想指出,使用stringstream来追加字符串比仅仅追加到一个字符串中要慢。stringstream非常适合将东西转换为字符串,但如果你只是从字符串构建字符串,那么这是一种效率较低的方法。append()和+=才是更好的选择。 - Jonathan M Davis
1
实际上,最近我写了一个测试程序,并发现使用stringstream将字符串拼接的速度大约是直接追加到字符串的2倍半——而且这还不包括在完成后从stringstream提取字符串的时间。这个具体数字可能不太准确,因为情况各异之类的原因,但很明显,使用stringstream构建流要比直接追加到字符串慢很多。 - Jonathan M Davis
我进行了一些测试,发现虽然稍微慢了一点,但只慢了20%。将一个字符串和10个具有10个字符的字符串相加,重复一百万次。差异为2秒(在我的机器上)。几乎不值得注意,因为任何处理器停顿都会立即消除这种差异。对我来说,胜利在于内存管理系统的开销更低。调用分配空间的次数较少,因为字符串流具有相对较大的缓冲区。 - Martin York
这可能是由于内部字符串缓冲区管理的特定实现(指数增长?)和字符串流缓冲区管理造成的。两者都不能保证以某种方式运行。 - Nikolai Fetissov
@Nikolai 确实如此。如果你真的关心并且想确保在你正在处理的特定程序和特定实现中哪个更快,你需要对其进行分析。然而,一般情况下,stringstream 可能会更慢,因此我建议使用 string 的 append() 或 +=,除非你需要 stringstream 的特殊功能,或者你真的关心性能并且已经对代码进行了分析,并发现在你的特定情况下 stringstream 更好。 - Jonathan M Davis

1

请使用模板添加字符串、char*和char,以形成字符串。

strlen:-

#include <iostream>
#include <cstring>

// it_pair to wrap a pair of iterators for a for(:) loop
template<typename IT> 
class it_pair
    {
    IT b;
    IT e;
public:
    auto begin() const
        {
        return b;
        }
    auto end() const
        {
        return e;
        }
    };

// string length
template<typename S> auto strlen(const S& s) -> decltype(s.size())
    {
    return s.size();
    }

auto strlen(char c) -> size_t
    {
    return 1u;
    }

auto strlen(const std::initializer_list<char>& il) -> size_t
    {
    return il.size();
    }

template<typename IT>
auto strlen(const it_pair<IT>& p)
    {
    auto len = size_t{};
    for(const auto& s:p)
        len += strlen(s);
    return len;
    }

template<typename S, typename ...SS> auto strlen(S s, SS&... ss) -> size_t
    {
    return strlen(s) + strlen(ss...);
    }

追加字符串。
// terminate recursion
template<typename TA, typename TB>
void append(TA& a, TB& b)
    {
    a.append(b);
    }

// special case for a character
template<>
void append<std::string, const char>(std::string& a, const char& b)
    {
    a.append(1, b);
    }

// special case for a collection of strings
template<typename TA, typename TB>
void append(TA& a, const it_pair<TB>& p)
    {
    for(const auto& x: p)
        a.append(x);
    }

// recursion append
template<typename TA, typename TB, typename ...TT>
void append(TA& a, TB& b, TT&... tt)
    {
    append(a, b);
    append(a, tt...);
    }

template<typename ...TT>
std::string string_add(const TT& ... tt)
    {
    std::string s;
    s.reserve(strlen(tt...));
    append(s, tt...);
    return s;
    }

template<typename IT>
auto make_it_pair(IT b, IT e)
    {
    return it_pair<IT>{b, e};
    }

template<typename T>
auto make_it_pair(const T& t)
    {
    using namespace std;
    return make_it_pair(cbegin(t), cend(t));
    }

主要示例
int main()
    {
    const char * s[] = {"vw", "xyz"};
    std::vector<std::string> v{"l", "mn", "opqr"};
    std::string a("a");
    std::string b("bc");
    std::string c("def");
    std::cout << string_add(a, b+c, "ghij", make_it_pair(v), 'k', make_it_pair(s));
    }

你应该创建一个标准化的提案 :) - Johan Boulé

0
如果你想要做到以下几点:
  • 高效地,即不会产生二次时间复杂度
  • 不覆盖原始变量
  • 不创建临时变量
那么这个方法可以胜任。
auto s = std::string(s1).append(s2).append(s3).append(sn);  

如果你喜欢事情有良好的格式

auto s = std::string(s1).append(s2)
                        .append(s3)
                        .append(sn)

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