C 中的字符串串联函数

String concatenation function in C++

本文关键字:函数 字符串      更新时间:2023-10-16

我知道+操作员加入2个std::string对象。并加入多个字符串的方法。但是我想知道表演。该答案说明了+操作员与方法(在Python中)相比为何效率低下。在C 标准库中加入多个字符串吗?

是否有类似的功能

操作员超载和方法调用之间没有性能差异 - 取决于调用上下文。在您应该担心的时刻,您是微观优化的。

这是一个抽象的示例,展示了概念。

class MyString 
{
public:
    // assume this class is implemented
    std::string operator+(const std::string &rhs) const
    {
        return concatenate(rhs);
    }
    std::string concatenate(const std::string &rhs) const
    {
        // some implementation
    }
};
MyString str1, str2;
// we can use the operator overload
std::string option1 = str1 + str2;
// or we can call a method
std::string option2 = str1.concatenate(str2);

运算符过载(大部分是)避免输入冗长的方法调用,例如后一个示例。它使代码更加干净和简洁。

如果您专门谈论2个以上字符串的性能,这是一种不同的情况。将对象分解为一个方法调用更具性能,因为它避免构建更多的临时对象。如果没有新的数据结构,您将无法做到这一点。

使用上面的类,我们将使用一个表达式中的+运算符将一堆对象串在一起。

std::string bigConcatenation = str1 + str2 + str1 + str2 + str1;

首先,如果您首先担心性能,您可能不会这样做。也就是说,这是对正在发生的事情的相当不错的近似值(假设编译器没有进行优化)。

std::string bigConcatenation = str1;
bigConcatenation = bigConcatenation + str2; 
bigConcatenation = bigConcatenation + str1;
bigConcatenation = bigConcatenation + str2;
bigConcatenation = bigConcatenation + str1;

这不是理想的原因,每个分配都会创建一个临时对象,将它们添加在一起,然后将结果分配回bigConcatenation

在不使用任何额外的容器的情况下,根据此答案,执行此操作的最佳方式就是这样(提示:在此过程中没有创建临时性)。

std::string bigConcatenation = str1;
bigConcatenation += str2;
bigConcatenation += str1;
bigConcatenation += str2;
bigConcatenation += str1;

串联字符串很少在C 中的瓶颈足够多,几乎没有人尝试优化它。

如果您遇到了这样一个重要的情况,那么这样做很重要,但是避免问题相当轻松,更改很小,因此a = b + c + d + e;保持 a = b + c + d + e;而不是诸如a.concat(b, c, d, e);之类的东西(我会补充的是,实际上是使工作良好的非常不乏味)。根据所涉及的类型,您可能需要添加代码以将列表中的第一个项目转换为正确的类型。对于显而易见的示例,我们不能超载operator+在字符串文字上工作,因此,如果您想将串联字符串文字合并在一起,则必须明确将第一个转换为其他类型。

"技巧"在称为表达模板的东西中(在这种情况下,它甚至不需要是模板)。您要做的是创建一个字符串构建器对象,该对象将其operator+超载以存储指针/引用到源字符串。反过来,这将转换为std::string,从而将所有源字符串的长度加在一起,将它们串联到std::string中,最后返回整个字符串。例如,代码看起来像这样:

#include <string>
#include <iostream>
#include <vector>
#include <numeric>
#include <cstring>
namespace string {
    class ref;
    class builder {
        std::vector<ref const *> strings;
    public:
        builder(ref const &s) { strings.push_back(&s); }
        builder & operator+(ref const &s) {
            strings.push_back(&s);
            return *this;
        }
        operator std::string() const;
    };
    class ref {
        char const *data;
        size_t N;
    public:
        ref(char const *s) : data(s), N(std::strlen(s)) {}
        ref(char const *s, size_t N) : data(s), N(N-1) {}
        size_t length() const { return N; }
        operator char const *() const { return data; }
        builder operator+(ref const &other) {
            return builder(*this) + other;
        }
    };
    builder::operator std::string() const {
        size_t length = std::accumulate(strings.begin(), strings.end(),
            size_t(),
            [](size_t a, auto s) { return a + s->length(); });
        std::string ret;
        ret.reserve(length);
        for (auto s : strings)
            ret.append(*s);
        return ret;
    }
} 
string::ref operator "" _s(char const *s, unsigned long N) { return string::ref(s, N); }
int main() {
    std::string a = "b"_s + "c" + "d" + "e";
    std::cout << a << "n";
}

请注意,当您上面编写代码时,如果您将所有文字转换为 string_ref s,则您(至少在理论上)获得了一些运行时效率:

    std::string a = "b"_s + "c"_s + "d"_s + "e"_s;

以这种方式,编译器在编译时测量每个文字的长度,并将长度直接传递给用户定义的文字操作员。如果您直接传递字符串文字,则该代码使用std::strlen在运行时测量长度。

您可以使用弦流记住添加指令#include
进入类的标题文件或类的源文件。无论哪个具有您的方法定义。