书籍《C++ Primer》说:
对于大多数应用程序而言,在使用字符串时,使用库字符串比使用C风格的字符串更安全,也更高效。
安全性已经被理解。那么,为什么C++字符串库更加高效呢?毕竟,在底层,字符串仍然表示为字符数组吧?
为了澄清,作者是在谈论程序员效率(已被理解)还是处理效率?
书籍《C++ Primer》说:
对于大多数应用程序而言,在使用字符串时,使用库字符串比使用C风格的字符串更安全,也更高效。
安全性已经被理解。那么,为什么C++字符串库更加高效呢?毕竟,在底层,字符串仍然表示为字符数组吧?
为了澄清,作者是在谈论程序员效率(已被理解)还是处理效率?
C-字符串通常更快,因为它们不调用 malloc/new。但是有些情况下,std::string
更快。函数 strlen()
的时间复杂度为 O(N),而 std::string::size()
的时间复杂度为 O(1)。
此外,当搜索子串时,在 C 字符串中需要在每个循环中检查 '\0'
,而在 std::string
中则不需要。在朴素的子串搜索算法中,这并不重要,因为您只需检查 i<s.size()
而不是检查 '\0'
。但是现代的高性能子串搜索算法会以多字节步长遍历字符串。在每个字节中需要进行'\0'
检查会拖慢速度。这就是 GLIBC memmem
快于 strstr
的原因。我进行了大量的算法基准测试。
这不仅适用于子串搜索算法。许多其他字符串处理算法对零终止字符串的处理速度较慢。
malloc/new
?当你需要一个动态大小的字符串时,你需要动态分配内存,这对于C字符串和std::string
都适用。除此之外,我认为在搜索子串时,C字符串和std::string
之间不应该有区别。最后,选择了50%错误答案之一,但好吧,接受哪个回答是James的决定。 - Christian Rauchar[1024]
过去就算了,但是嗯。但你是对的,在某些情况下,小的字符数组确实是合适的,并且不需要任何动态分配,但我认为这些情况远非通常情况,特别是在进行复杂的字符串处理时。 - Christian Rauchar *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周期方面更加高效,而且还可以提高程序员的效率!
for (int i = 0; s[i]; ++i)
,这种方法非常高效。但是关于字符串类存储字符串长度并能在O(1)时间内获得长度的问题,仍需讨论。 - Daniel Fleischmanstrlen
函数被打上一个属性标记,指示编译器函数调用的结果仅取决于参数(除了返回值以外没有副作用)。这使得编译器可以从循环中分离出strlen
函数。尽管如此,这是实现质量,并且该优化在其他编译器/库实现中不可用。 - David Rodríguez - dribeasfor(size_t i = 0 ; i < strlen(s) ; ++i) //这是一个高效的循环吗?不是。
-- 因为这样做是完全错误的,所以被下投票了。 - Jim Balterstd::string
知道它的长度,这使得许多操作更快。
例如,给定:
const char* c1 = "Hello, world!";
const char* c2 = "Hello, world plus dog!";
std::string s1 = c1;
std::string s2 = c2;
s1.length()
比strlen(c1)
更快。在比较方面,strcmp(c1, c2)
需要比较多个字符才能确定字符串不相等,但是s1 == s2
可以立即判断长度不同并返回false。strcat(buf, c1)
需要在buf
中查找空终止符以确定数据追加的位置,但s1 += s2
已经知道了s1
的长度,可以立即将新字符追加到正确的位置。std::string
每次增长时都会分配额外的空间,这意味着未来的追加操作不需要重新分配空间。有些情况下,std::string
可能比 char[]
更好。例如,C风格字符串通常没有传递显式长度,而是通过 NUL 结束符来隐式定义长度。
这意味着一个循环不断地对一个 char[]
执行 strcat
操作实际上是执行 O(n²) 的工作,因为每次 strcat
都需要处理整个字符串以确定插入点。相反,std::string
只需要执行将新字符复制到末尾的操作(可能还需要重新分配存储空间,但为了公平比较,您必须事先知道最大大小并且使用 reserve()
)。
size
方法必须为O(1),这实际上意味着相同的事情)。因此,每当您需要在C字符串中查找NUL字符(因此需要遍历整个字符串)时,您可以在常数时间内获取大小。这种情况经常发生,例如在复制或连接字符串时,并且预先分配一个新字符串,您需要知道其大小。但我不知道作者是否指的是这个,或者它在实践中是否有很大的区别,但它仍然是一个有效的观点。这里是一个简短的观点。
首先,C++字符串是对象,因此在面向对象的语言中使用它们更加一致。
其次,标准库提供了许多有用的字符串函数、迭代器等。所有这些都是你不必再编写的东西,所以你节省了时间,并且可以确保这些代码(几乎)没有错误。
最后,C字符串是指针,在你刚开始学习时很难理解,而且会带来复杂性。由于在C++中引用优于指针,因此使用std::string而不是C字符串更有意义。
使用面向对象的字符串类型在许多情况下可能不如使用标准C字符串高效,但需要找出最佳的分配和重用方式。另一方面,编写为最优化分配和重用字符串的代码可能非常脆弱,对需求的轻微更改可能需要对代码进行大量棘手的微调——未能完美地调整代码可能会导致明显且严重的错误或更加隐蔽但更加严重的错误。避免使用标准C字符串的代码脆性的最实用方法是非常保守地设计它。记录最大输入数据大小,截断任何过大的内容,并为所有内容使用大缓冲区。这种方法可行,但效率不高。
相比之下,如果使用面向对象的字符串类型,它们使用的分配模式可能不是最优的,但很可能比“分配所有大型内容”的方法更好。因此,它们结合了手动优化代码方法的大部分运行时效率和比“分配所有大型内容”的方法更好的安全性。