优秀的编程知识分享平台

网站首页 > 技术文章 正文

C++ 将容器元素连接成字符串(c++中的容器)

nanyue 2024-10-11 13:42:36 技术文章 6 ℃

有时,库中没有算法可以完成手头的任务。我们可以使用迭代器,使用与算法库相同的技术,轻松编写一个。

例如,我们经常需要将容器中的元素用分隔符连接成字符串。一个常见的解决方案是使用一个简单的 for() 循环:

for(auto v : c) cout << v << ', ';


这个简单解决方案的问题是它留下了一个尾随分隔符:

vector<string> greek{ "alpha", "beta", "gamma", "delta", "epsilon" };
for(auto v : greek) cout << v << ", ";
cout << '\n';


输出:

alpha, beta, gamma, delta, epsilon,


这在测试环境中可能没问题,但在任何生产系统中,那个尾随的逗号都是不可接受的。

ranges::views 库有一个 join() 函数,但它没有提供分隔符:

auto greek_view = views::join(greek);


views::join() 函数返回一个 ranges::view 对象。这需要一个单独的步骤来显示或转换为字符串。我们可以使用 for() 循环遍历视图:

for(const char c : greek_view) cout << c;
cout << '\n';


输出看起来像这样:

alphabetagammadeltaepsilon


它都在那里,但我们需要在元素之间正确地分隔,以使其对我们的目的有用。

由于算法库中没有满足我们需求的函数,我们将编写一个。

如何做到这一点…

对于这个食谱,我们将把容器的元素连接成一个带有分隔符的字符串:

在我们的 main() 函数中,我们声明一个字符串向量:

int main() {
    vector<string> greek{ "alpha", "beta", "gamma", "delta", "epsilon" };
    ...
}


现在,让我们编写一个简单的 join() 函数,使用 ostream 对象将元素用分隔符连接起来:

namespace bw {
    template<typename I>
    ostream& join(I it, I end_it, ostream& o,
                  string_view sep = "") {
        if(it != end_it) o << *it++;
        while(it != end_it) o << sep << *it++;
        return o;
    }
}


我把这放在我自己的 bw 命名空间中,以避免名称冲突。

我们可以用 cout 这样调用它:

bw::join(greek.begin(), greek.end(), cout, ", ") << '\n';


因为它返回 ostream 对象,我们可以在它后面加上 << 向流中添加一个换行符。

输出:

alpha, beta, gamma, delta, epsilon


我们通常想要一个字符串,而不是直接写入 cout。我们可以为返回字符串对象的版本重载这个函数:

template<typename I>
string join(I it, I end_it, string_view sep = "") {
    ostringstream ostr;
    join(it, end_it, ostr, sep);
    return ostr.str();
}


这也在 bw 命名空间中。这个函数创建了一个 ostringstream 对象来传递给 bw::join() 的 ostream 版本。它从 ostringstream 对象的 str() 方法返回一个字符串对象。

我们可以使用它:

string s = bw::join(greek.begin(), greek.end(), ", ");
cout << s << '\n';


输出:

alpha, beta, gamma, delta, epsilon


让我们添加一个最终的重载,使这个更容易使用:

string join(const auto& c, string_view sep = "") {
    return join(begin(c), end(c), sep);
}


这个版本只接受一个容器和一个分隔符,这应该能满足大多数用例:

string s = bw::join(greek, ", ");
cout << s << '\n';


输出:

alpha, beta, gamma, delta, epsilon


它是如何工作的…

这个食谱的大部分工作由迭代器和 ostream 对象完成:

namespace bw {
    template<typename I>
    ostream& join(I it, I end_it, ostream& o,
                  string_view sep = "") {
        if(it != end_it) o << *it++;
        while(it != end_it) o << sep << *it++;
        return o;
    }
}


分隔符在第一个元素之后,每个连续元素之间,最后一个元素之前停止。这意味着我们可以在每个元素之前添加一个分隔符,跳过第一个,或者在每个元素之后添加一个分隔符,跳过最后一个。如果我们测试并跳过第一个元素,逻辑会更简单。我们在 while() 循环之前的一行就是这样做的:

if(it != end_it) o << *it++;


一旦我们把第一个元素排除在外,我们可以简单地在每个剩余元素之前添加一个分隔符:

while(it != end_it) o << sep << *it++;


我们返回 ostream 对象作为方便。这允许用户轻松地向流中添加换行符或其他对象:

bw::join(greek.begin(), greek.end(), cout, ", ") << '\n';


输出:

alpha, beta, gamma, delta, epsilon


还有更多…

与任何库算法一样,join() 函数将适用于支持前向迭代器的任何容器。例如,这是来自 numbers 库的双精度常数列表:

namespace num = std::numbers;
list<double> constants { num::pi, num::e, num::sqrt2 };
cout << bw::join(constants, ", ") << '\n';


输出:

3.14159, 2.71828, 1.41421


它甚至可以与 ranges::view 对象一起使用,比如前面食谱中定义的 greek_view:

cout << bw::join(greek_view, ":") << '\n';


输出:

a:l:p:h:a:b:e:t:a:g:a:m:m:a:d:e:l:t:a:e:p:s:i:l:o:n

Tags:

最近发表
标签列表