C-String 与 C++Strings 的效率

Posted

技术标签:

【中文标题】C-String 与 C++Strings 的效率【英文标题】:Efficiency of C-String vs C++Strings 【发布时间】:2012-08-25 17:44:29 【问题描述】:

C++ 入门说

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

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

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

【问题讨论】:

复杂性更低 = 生产力更高 = 有更多时间优化慢代码 = 更高效率。 我们说的是程序员效率还是处理效率? @scientiaesthete 是的,因此它们有助于计算机程序的整体能源最小化和更清洁、更和平的世界;) 我们也可以问一下它是在谈论内存效率还是处理器效率。 另外,不管字符串是什么编码,一切都成立吗?例如,东亚语言中使用的多字节编码或 Unicode 的 UTF-8? 【参考方案1】:

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

此外,当您搜索子字符串时,在 C 字符串中,您需要在每个循环中检查 '\0',在 std::string 中 - 您不需要。在一个简单的子字符串搜索算法中,这并不重要,因为您不需要检查'\0',而是需要检查i<s.size()。但是现代高性能子字符串搜索算法以多字节步骤遍历字符串。并且需要'\0' 检查每个字节会减慢它们的速度。这就是 GLIBC memmemstrstr 快 2 倍的原因。我做了a lot of benchmarking 的子串算法。

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

【讨论】:

C 字符串在哪种情况下不需要调用malloc/new?每当您想要一个动态大小的字符串时,您都需要动态分配内存,这适用于 C 字符串以及 std::strings。除此之外,我认为搜索子字符串是一种算法,C 字符串和std::strings 之间不应该有区别。再次选择了略微错误的 50% 之一,但好吧,接受哪个答案是詹姆斯的选择。 @ChristianRau - 你需要重新阅读我的帖子。您还有机会对 c-string 和 std::string 子字符串函数进行基准测试吗?我做到了。 不,我没有进行分析,只是在考虑搜索子字符串时,我不一定认为每次都需要计算长度,尽管我可能错了。其中我知道 O(1) 长度是主要优势(这也是其他正确答案所写的)。但即使在重读之后,我也看不到 C 字符串如何“不调用 malloc/new”,这只是垃圾,也是我在你的回答中的主要批评点。 您可以将 c-string(或几乎任何其他类型)放入动态分配的内存中,但您不必这样做。如果您动态分配 C 字符串 - 您将这样做,而不是 C 字符串的成员函数。 C 字符串(如 int、double 或任何 POD 类型)不称自己为 malloc/new。 好吧,指针当然不会自己分配。但是 c 字符串通常“不调用 malloc/new”,因为使用简单的编译时数组不是通常的工作方式使用字符串,尤其是考虑到任何字符串处理算法。当然,您总是可以向它扔一个char[1024] 并收工,但是很好。但是你是对的,在某些情况下,一个小的 char 数组是合适的,不需要任何动态分配,但我认为这些情况与往常相去甚远,尤其是在进行复杂的字符串处理时。【参考方案2】:

为什么 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 周期方面有效,而且还提高了程序员的效率!

【讨论】:

遍历 C 字符串的常用方法是:for (int i = 0; s[i]; ++i),这很有效。参数应该是关于存储字符串大小的字符串类,使其在 O(1) 中可用。 @DanielFleischman:你误读了我的回答。我给出了这个例子,首先问“你见过这样的循环吗?”,然后添加O(N) vs O(1),在其余答案中解释其他指针。 (所以我不认为downvote 是合理的)。 @Nawaz:在 gcc 的 C 标准库实现中,strlen 带有一个属性标记,该属性指示编译器函数调用的结果仅取决于参数(除返回值)。这允许编译器从循环中提取strlen。话虽如此,这就是实现的质量,并且该优化不适用于其他编译器/库实现。 @DavidRodríguez-dribeas 这取决于编译器的质量,而不是应该依赖于这种优化 for(size_t i = 0 ; i &lt; strlen(s) ; ++i) //Is it efficent loop? No. -- 投反对票,因为这完全是假的。没有人这样做。【参考方案3】:

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

【讨论】:

【参考方案4】:

在某些情况下,std::string 可能会击败char[]。例如,C 风格的字符串通常没有明确的长度传递——相反,NUL 终止符隐式地定义了长度。

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

【讨论】:

我确信大多数 C 开发人员都足够聪明地自己存储大小。它的速度大致相同,但由于代码更多 - 更多变量、更多函数参数等,它也更容易出错且更难理解。【参考方案5】:

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

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

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

【讨论】:

【参考方案6】:

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

Cstrings 只是字符数组。当您实时工作时应该使用它们;当您不完全了解您手头有多少内存空间时。如果您使用的是 cstrings,则必须注意内存分配,然后通过 strcpy 或逐个字符将数据复制到其中,然后在使用后解除分配等。 所以如果你想避免一堆头痛,最好使用字符串库中的字符串。

字符串提高程序效率但降低处理效率(虽然不一定)。反之亦然是 cstrings

【讨论】:

std::string 并不一定会降低处理效率。 这在某种程度上与问题中引用的内容相矛盾,而不是解释为什么引用如此说。这不是关于头痛,而是关于处理效率。【参考方案7】:

这是一个简短的观点。

首先,C++字符串是对象,所以在面向对象的语言中使用更加一致。

然后,标准库为字符串、迭代器等提供了许多有用的函数。所有这些东西都是您不必再次编码的东西,因此您可以节省时间并且确定这些代码是 (几乎)没有错误。

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

希望我能帮上忙。

【讨论】:

C++ 不是面向对象的语言。 @JonathanWakely 它不仅仅是一种面向对象的语言。但是,在里面进行面向对象编程是绝对可以的,在我看来,这是“面向对象语言”唯一合理的定义。 面向对象编程是 C 和 C++ 的基本区别。最初 C++ 被命名为“C with classes”。 @CodingMash,还有很多其他的区别,比如模板,跟OO没有关系。 C++ 是一种多范式语言,而不是 OO 语言。您可以用 C 编写 OO 代码(付出足够的努力),但这并不能使它成为一种“OO 语言” 我只是说基本的区别。你是对的,还有很多其他模板、命名空间等。我只是在评论“c++ 不是面向对象的语言”。它可能不是纯粹的 oop 语言,它也有程序范式(无意冒犯)【参考方案8】:

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

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

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

【讨论】:

以上是关于C-String 与 C++Strings 的效率的主要内容,如果未能解决你的问题,请参考以下文章

c-string用户输入为空时如何发送错误消息?

使用带有 std::cout 的 volatile c-string [重复]

Codeforces Round #423 Div. 2 C-String Reconstruction(思维)

C++ string append方法的常用用法

STL标准库中string,vector,list使用的异同点

❥关于C++之数组与指针