C 字符串与 C++ 字符串的效率

Efficiency of C string vs C++ strings

本文关键字:字符串 效率 C++      更新时间:2023-10-16

入门C++书说

对于大多数应用来说,除了更安全之外,它还更多高效使用库字符串而不是 C 样式字符串

安全是理解的。为什么C++字符串库更高效?毕竟,在这一切之下,字符串不是仍然表示为字符数组吗?

澄清一下,作者是在谈论程序员效率(理解(还是处理效率?

C 字符串通常更快,因为它们不调用 malloc/new。但在某些情况下,std::string更快。函数 strlen() 是 O(N(,但std::string::size()是 O(1(。

此外,当您搜索子字符串时,在 C 字符串中,您需要检查每个周期的'',以std::string 为单位 - 您不需要。在朴素的子字符串搜索算法中,这并不重要,因为您需要检查i<s.size()而不是检查''。但现代高性能子字符串搜索算法以多字节步骤遍历字符串。而且需要对每个字节进行''检查会减慢它们的速度。这就是为什么 GLIBC memmemstrstr 快 x2 倍的原因。我对子字符串算法做了很多基准测试。

这不仅适用于子字符串搜索算法。对于以零结尾的字符串,许多其他字符串处理算法的速度较慢。

为什么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) .无需手动优化它,这通常会使代码看起来很难看且难以阅读。与char*相比,带有std::string的结果代码几乎总是整洁干净。

另请注意,std::string不仅使您的代码在 CPU 周期方面更有效率,而且还提高了程序员的效率!

std::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每次增长时都会分配额外的空间,这意味着未来的追加操作不需要重新分配。

在某些情况下std::string可能会击败char[]。例如,C 样式字符串通常没有传递的显式长度,而是 NUL 终止符隐式定义长度。

这意味着连续strcat s到char[]上的循环实际上是在执行O(n²(工作,因为每个strcat都必须处理整个字符串才能确定插入点。相比之下,std::string连接到字符串末尾需要执行的唯一工作是复制新字符(并可能重新分配存储 — 但为了公平比较,您必须事先知道最大大小并reserve()它(。

嗯,一个明显而简单的事情是,它们实际上可以提高效率(关于运行时(是,它们将字符串的长度与数据存储在一起(或者至少它们的size方法必须是O(1(,这实际上是一样的(。

因此,每当您需要在 C 字符串中找到 NUL 字符(从而遍历整个字符串一次(时,您都可以在恒定时间内获得大小。这种情况经常发生,例如,在复制或连接字符串并因此事先分配一个新的字符串时,您需要知道其大小。

但我不知道这是作者的意思,还是在实践中产生了巨大的影响,但这仍然是一个有效的观点。

字符串是包含字符数组及其大小和其他功能的对象。最好使用字符串库中的字符串,因为它们可以使您免于分配和解分配内存,从而防止内存泄漏和其他指针危险。但由于字符串是对象,因此它们会在内存中占用额外的空间。

C 字符串只是字符数组。当您实时工作时,使用它们;当您不完全知道手头有多少内存空间时。如果您使用的是 C 字符串,则必须注意内存分配,然后通过 strcpy 或逐个字符将数据复制到其中,然后在使用后进行释放,等等。

因此,如果您想避免一堆麻烦,最好使用字符串库中的字符串。

字符串提高了程序效率,但降低了处理效率(尽管不一定(。反之亦然,使用 C 字符串。

C 样式字符串的困难在于,除非知道包含它们的数据结构,否则实际上无法对它们做太多事情。 例如,当使用"strcpy"时,必须知道目标缓冲区是可写的,并且有足够的空间来容纳源中第一个零字节之前的所有内容(当然,在太多的情况下,人们并不真正知道...... 很少有库例程为按需分配空间提供任何支持,我认为所有那些通过无条件分配它来工作的(因此,如果一个人有一个缓冲区,空间为 1000 字节,并且想要复制一个 900 字节的字符串,使用这些方法的代码将不得不放弃 1000 字节的缓冲区,然后创建一个新的 900 字节缓冲区, 即使简单地重用 1000 字节缓冲区可能更好(。

在许多情况下,使用面向对象的字符串类型不如使用标准 C 字符串有效,但要找出分配和重用事物的最佳方法。 另一方面,为优化分配和重用字符串而编写的代码可能非常脆弱,对需求的轻微更改可能需要对代码进行许多棘手的小调整 - 未能完美地调整代码可能会导致错误,这些错误可能是明显和严重的,或者微妙但更严重。 避免使用标准 C 字符串的代码脆弱的最实用方法是非常保守地设计它。 记录最大输入数据大小,截断太大的任何内容,并对所有内容使用大缓冲区。 可行,但效率不高。

相比之下,如果使用面向对象的字符串类型,他们使用的分配模式可能不是最佳的,但可能比"分配所有大东西"方法更好。 因此,它们将手动优化代码方法的大部分运行时效率与比"分配所有大东西"方法更好的安全性相结合。

这是一个简短的观点。

首先,C++字符串是对象,因此在面向对象的语言中使用它们更一致。

然后,标准库附带了许多用于字符串、迭代器等的有用函数。所有这些东西都是你不必再次编码的东西,所以你赢得了时间,你确信这段代码(几乎(没有错误。

最后,C 字符串是新手时难以理解的指针,它们会带来复杂性。由于引用优先于C++中的指针,因此使用 std::string 而不是 C 字符串更有意义。