C++ Primer 说
对于大多数应用程序,除了更安全之外,使用库字符串而不是 C 样式的字符串也更有效
安全是了解的。为什么 C++ 字符串库更高效?毕竟,在这一切之下,字符串不是仍然表示为字符数组吗?
为了澄清,作者是在谈论程序员效率(理解)还是处理效率?
C 字符串通常更快,因为它们不调用 malloc/new。但也有std::string
更快的情况。函数strlen()
是 O(N),但是std::string::size()
是 O(1)。
此外,当您搜索子字符串时,在 C 字符串中,您需要'\0'
在每个循环中检查std::string
- 您不需要。在幼稚的子字符串搜索算法中,这并不重要,因为'\0'
您需要检查i<s.size()
. 但是现代高性能子字符串搜索算法以多字节步骤遍历字符串。并且需要'\0'
检查每个字节会减慢它们的速度。这就是 GLIBCmemmem
比strstr
. 我对子字符串算法做了很多基准测试。
这不仅适用于子字符串搜索算法。对于以零结尾的字符串,许多其他字符串处理算法较慢。
为什么 C++ 字符串库更高效?毕竟,在这一切之下,字符串不是仍然表示为字符数组吗?
因为如果不仔细编写,使用char*
或char[]
更有可能效率低下的代码。例如,你见过这样的循环:
char *get_data();
char const *s = get_data();
for(size_t i = 0 ; i < strlen(s) ; ++i) //Is it efficent loop? No.
{
//do something
}
那效率高吗?不。 的时间复杂度strlen()
是O(N)
,此外,在上面的代码中,它是在每次迭代中计算的。
现在你可能会说“如果我strlen()
只调用一次,我就可以让它变得高效”。. 当然可以。但是您必须自己并且有意识地进行所有这些优化。如果你错过了什么,你就错过了 CPU 周期。但是有了std::string
,许多这样的优化是由类本身完成的。所以你可以这样写:
std::string get_data();
std::string const & s = get_data(); //avoid copy if you don't need it
for(size_t i = 0 ; i < s.size() ; ++i) //Is it efficent loop? Yes.
{
//do something
}
那效率高吗?是的。的时间复杂度size()
为O(1)
。无需手动优化,这通常会使代码看起来难看且难以阅读。与 相比,生成的代码std::string
几乎总是整洁干净char*
。
另请注意,std::string
这不仅使您的代码在 CPU 周期方面有效,而且还提高了程序员的效率!
Astd::string
知道它的长度,这使得许多操作更快。
例如,给定:
const char* c1 = "Hello, world!";
const char* c2 = "Hello, world plus dog!";
std::string s1 = c1;
std::string s2 = c2;
strlen(c1)
比 慢s1.length()
。对于比较,strcmp(c1, c2)
必须比较几个字符以确定字符串不相等,但s1 == s2
可以判断长度不一样并立即返回 false。
其他操作也受益于提前知道长度,例如strcat(buf, c1)
必须在其中找到空终止符buf
才能找到附加数据s1 += s2
的位置,但已经知道长度s1
并且可以立即将新字符附加到正确的位置。
在内存管理方面,std::string
每次增长时都会分配额外的空间,这意味着未来的追加操作不需要重新分配。
在某些情况下, astd::string
可能会击败 a char[]
。例如,C 风格的字符串通常没有明确的长度传递——相反,NUL 终止符隐式地定义了长度。
这意味着连续strcat
s 到 a的循环char[]
实际上正在执行 O(n²) 工作,因为每个循环都strcat
必须处理整个字符串才能确定插入点。相比之下,astd::string
连接到字符串末尾需要执行的唯一工作是复制新字符(并且可能重新分配存储空间——但为了公平比较,您必须事先知道最大大小和reserve()
它) .
字符串是在自身内部包含字符数组及其大小和其他功能的对象。最好使用字符串库中的字符串,因为它们可以避免分配和释放内存,避免内存泄漏和其他指针危险。但是由于字符串是对象,所以它们在内存中占用了额外的空间。
Cstrings 只是字符数组。当您实时工作时应该使用它们;当您不完全了解您手头有多少内存空间时。如果您使用的是 cstrings,则必须注意内存分配,然后通过 strcpy 或逐个字符将数据复制到其中,然后在使用后解除分配等。所以如果你想避免一堆头痛,最好使用字符串库中的字符串。
字符串提高程序效率但降低处理效率(虽然不一定)。反之亦然与 cstrings
好吧,一个明显而简单的事情是,它们实际上如何更有效(关于运行时),它们将字符串的长度与数据一起存储(或者至少它们的size
方法必须是 O(1),这实际上是一样的) .
因此,每当您需要在 C 字符串中查找 NUL 字符(从而遍历整个字符串一次)时,您都可以在恒定时间内获得大小。这种情况经常发生,例如在复制或连接字符串并因此预先分配一个新字符串时,您需要知道其大小。
但我不知道这是否是作者的意思,或者它是否在实践中产生了巨大的差异,但这仍然是一个有效的观点。
这是一个简短的观点。
首先,C++ 字符串是对象,所以在面向对象的语言中使用它们更加一致。
然后,标准库为字符串、迭代器等提供了许多有用的函数。所有这些东西都是您不必再次编码的东西,因此您可以获得时间,并且您确信这段代码(几乎)没有错误.
最后,C 字符串是指针,当您是新手时有点难以理解,并且它们带来了复杂性。由于在 C++ 中引用优先于指针,因此使用 std::string 而不是 C 字符串更有意义。
希望我有所帮助。
C 风格字符串的困难在于,除非知道它们包含在其中的数据结构,否则真的无法对它们做太多事情。例如,当使用“strcpy”时,必须知道目标缓冲区是可写的,并且有足够的空间来容纳源中第一个零字节之前的所有内容(当然,在太多的情况下,实际上并没有肯定知道...)。很少有库例程提供对按需分配空间的任何支持,我认为所有那些通过无条件分配来工作的库例程(所以如果一个缓冲区有一个 1000 字节的空间,并且想要复制一个 900 字节的字符串,代码使用这些方法将不得不放弃 1000 字节缓冲区,然后创建一个新的 900 字节缓冲区,即使简单地重用 1000 字节缓冲区可能更好)。
在许多情况下,使用面向对象的字符串类型不如使用标准 C 字符串高效,但要找出分配和重用事物的最佳方式。另一方面,为优化分配和重用字符串而编写的代码可能非常脆弱,对需求的微小更改可能需要对代码进行大量棘手的小调整——未能完美地调整代码可能会导致错误可能是明显而严重的,也可能是微妙的,但更严重。避免使用标准 C 字符串的代码脆弱的最实用方法是非常保守地设计它。记录最大输入数据大小,截断任何太大的内容,并为所有内容使用大缓冲区。可行,但效率不高。
By contrast, if one uses the object-oriented string types, the allocation patterns they use will likely not be optimal, but will likely be better than the 'allocate everything big' approach. They thus combine much of the run-time efficiency of the hand-optimized-code approach with safety that's better than the 'allocate everything big' approach.