6

可能重复:
简单的​​字符串连接

昨天,在我写这篇文章的时候,有人问了 SO

如果我有一个字符串在 python 中x='wow' 应用该函数:add

x='wow'
x.add(x)
'wowwow'

我怎么能在 C++ 中做到这一点?

随着add(不存在)纠正为__add__(标准方法),这是一个深刻而有趣的问题,涉及微妙的低级细节,高级算法复杂性考虑,甚至线程!简洁的方式。

将原始问题 作为我自己的问题重新发布,因为在删除之前我没有机会提供正确答案,并且我努力恢复原始问题,以便我可以帮助增加对这些问题的一般理解,失败的。

我把原来的标题“select python or C++”改成了……

  • 在 C++ 中,CPython 字符串连接的等价物是什么?

从而缩小了问题的范围。

4

1 回答 1

10

代码片段的一般含义。

给定的代码片段

x = 'wow'
x.__add__( x )

在 Python 2.x 和 Python 3.x 中有不同的含义。

在 Python 2.x 中,字符串默认是窄字符串,每个编码单元一个字节,对应于基于 C++char的字符串。

在 Python 3.x 中,字符串是宽字符串,保证代表 Unicode,对应于基于 C++ wchar_t的字符串的实际使用,同样每个编码单元具有未指定的 2 或 4 个字节。

忽略效率,该__add__方法在两个主要 Python 版本中的行为相同,对应于 C+++运算符 for std::basic_string (ie, for std::stringand std::wstring),例如引用CPython 3k 文档

object.__add__(self, other)
... 评估表达式x + y,其中x是具有__add__()方法的类的实例,x.__add__(y)被调用。

例如,CPython 2.7 代码

x = 'wow'
y = x.__add__( x )
print y

通常会写成

x = 'wow'
y = x + x
print y

并对应于此 C++ 代码:

#include <iostream>
#include <string>
using namespace std;

int main()
{
    auto const x = string( "wow" );
    auto const y = x + x;
    cout << y << endl;
}

与为原始问题给出的许多不正确答案的主要区别 在于,C++ 对应是一个表达式,而不是一个更新

可能很自然地认为方法名称__add__表示字符串对象的值的更改,更新,但就可观察行为而言,Python 字符串是不可变字符串。只要可以在 Python 代码中直接观察到,它们的值就不会改变。这与 Java 和 C# 中的相同,但与 C++ 的可变std::basic_string字符串非常不同。

CPython 中的二次到线性时间优化。

CPython 2.4 添加 了以下 优化,仅适用于窄字符串

s = s + "abc"表单语句中的字符串连接s += "abc" 现在在某些情况下可以更有效地执行。这种优化不会出现在其他 Python 实现中,比如 Jython,所以你不应该依赖它;join()当您想有效地将​​大量字符串粘合在一起时,仍然建议使用字符串的方法。(由阿明·里戈提供。)

这听起来可能不多,但在适用的情况下,这种优化会减少从二次时间O( n 2 ) 到线性时间O( n ) 的串联序列,最终结果的长度为n

首先,优化用更新替换串联,例如

x = x + a
x = x + b
x = x + c

或就此而言

x = x + a + b + c

被替换为

x += a
x += b
x += c

在一般情况下,将有许多对所引用的字符串对象的x 引用,并且由于 Python 字符串对象必须看起来是不可变的,因此第一次更新分配不能更改该字符串对象。因此,通常,它必须创建一个全新的字符串对象,并将该(引用)分配给x.

此时x持有对该对象的唯一引用。这意味着对象可以通过附加的更新分配来更新b,因为没有观察者。同样对于c.

这有点像量子力学:你无法观察到这个肮脏的事情正在发生,而且当有可能有人观察到这些阴谋时,它永远不会发生,但你可以通过收集的有关性能的统计数据推断它一定是在进行,因为线性时间与二次时间完全不同!

线性时间是如何实现的?好吧,通过更新,可以完成与 C++ 中相同的缓冲区加倍策略std::basic_string,这意味着只需在每次缓冲区重新分配时复制现有缓冲区内容,而不是每次追加操作。这意味着复制的 总成本在最坏的情况下与最终字符串大小呈线性关系,就像总和(表示每次缓冲区翻倍时的复制成本)1 + 2 + 4 + 8 + ... + N 小于2*N。

C++ 中的线性时间字符串连接表达式。

为了忠实再现 C++ 中的 CPython 代码片段,

  • 应该捕获操作的最终结果和表达式性质,

  • 并且应该捕获它的性能特征!

__add__CPython到 C++的直接翻译std::basic_string +无法可靠地捕获 CPython 线性时间。编译器可以以与 CPython 优化相同的方式优化C+++字符串连接 。或者不是——这意味着有人告诉初学者,Python 线性时间运算的 C++ 等价物是具有二次时间的东西——嘿,这就是你应该使用的……

对于性能特征,C+++=是基本答案,但是,这并没有抓住 Python 代码的表达性质。

自然的答案是一个线性时间 C++字符串构建器类,它将连接表达式转换为一系列+=更新,以便 Python 代码

from __future__ import print_function

def foo( s ):
    print( s )

a = 'alpha'
b = 'beta'
c = 'charlie'
foo( a + b + c )    # Expr-like linear time string building.

大致对应

#include <string>
#include <sstream>

namespace my {
    using std::string;
    using std::ostringstream;

    template< class Type >
    string stringFrom( Type const& v )
    {
        ostringstream stream;
        stream << v;
        return stream.str();
    }

    class StringBuilder
    {
    private:
        string      s_;

        template< class Type >
        static string fastStringFrom( Type const& v )
        {
            return stringFrom( v );
        }

        static string const& fastStringFrom( string const& s )
        { return s; }

        static char const* fastStringFrom( char const* const s )
        { return s; }

    public:
        template< class Type >
        StringBuilder& operator<<( Type const& v )
        {
            s_ += fastStringFrom( v );
            return *this;
        }

        string const& str() const { return s_; }
        char const* cStr() const { return s_.c_str(); }

        operator string const& () const { return str(); }
        operator char const* () const { return cStr(); }
    };
}  // namespace my

#include <iostream>
using namespace std;
typedef my::StringBuilder S;

void foo( string const& s )
{
    cout << s << endl;
}

int main()
{
    string const    a   = "alpha";
    string const    b   = "beta";
    string const    c   = "charlie";

    foo( S() << a << b << c );      // Expr-like linear time string building.
}
于 2012-10-23T00:30:17.520 回答