C 字符串与 C++ 字符串的效率
Efficiency of C string vs C++ strings
入门C++书说
对于大多数应用来说,除了更安全之外,它还更多高效使用库字符串而不是 C 样式字符串
安全是理解的。为什么C++字符串库更高效?毕竟,在这一切之下,字符串不是仍然表示为字符数组吗?
澄清一下,作者是在谈论程序员效率(理解(还是处理效率?
C 字符串通常更快,因为它们不调用 malloc/new。但在某些情况下,std::string
更快。函数 strlen()
是 O(N(,但std::string::size()
是 O(1(。
此外,当您搜索子字符串时,在 C 字符串中,您需要检查每个周期的' '
,以std::string
为单位 - 您不需要。在朴素的子字符串搜索算法中,这并不重要,因为您需要检查i<s.size()
而不是检查' '
。但现代高性能子字符串搜索算法以多字节步骤遍历字符串。而且需要对每个字节进行' '
检查会减慢它们的速度。这就是为什么 GLIBC memmem
比 strstr
快 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 字符串更有意义。