为啥在 C++ 中更喜欢 char* 而不是字符串?

Posted

技术标签:

【中文标题】为啥在 C++ 中更喜欢 char* 而不是字符串?【英文标题】:Why do you prefer char* instead of string, in C++?为什么在 C++ 中更喜欢 char* 而不是字符串? 【发布时间】:2009-11-19 15:26:16 【问题描述】:

我是一名尝试编写 C++ 代码的 C 程序员。我听说 C++ 中的string 在安全性、性能等方面比char* 好,但有时char* 似乎是更好的选择。有人建议程序员不要在 C++ 中使用char*,因为我们可以做char* 可以用字符串做的所有事情,而且更安全、更快。

你曾经在 C++ 中使用过char* 吗?具体条件是什么?

【问题讨论】:

【参考方案1】:

使用std::string 更安全,因为您无需担心为字符串分配/释放内存。 C++ std::string 类可能在内部使用 char* 数组。但是,该类将为您管理内部数组的分配、重新分配和解除分配。这消除了使用原始指针带来的所有常见风险,例如内存泄漏、缓冲区溢出等。

此外,它也非常方便。您可以复制字符串、附加到字符串等,而无需手动提供缓冲区空间或使用 strcpy/strcat 等函数。使用 std::string 就像使用 =+ 运算符一样简单。

基本上是:

 std::string s1 = "Hello ";
 std::string s2 = s1 + "World";

对比...

 const char* s1 = "Hello";
 char s2[1024]; // How much should I really even allocate here?
 strcpy(s2, s1);
 strcat(s2, " World ");

编辑:

针对您对在 C++ 中使用 char* 所做的修改:许多 C++ 程序员会声称您应该永远使用 char*,除非您正在使用一些需要它,在这种情况下,您可以使用std::string::c_str() 函数将std::string 转换为const char*

但是,我想说 C++ 中有一些 C 数组的合法用途。例如,如果性能绝对关键,堆栈上的小型 C 数组可能是比std::string 更好的解决方案。您也可能正在编写一个程序,您需要对内存分配/释放进行绝对控制,在这种情况下您将使用char*。此外,正如 cmets 部分所指出的,std::string 不能保证为您提供连续的、可写的缓冲区 *,因此如果您需要您的程序,您不能直接从文件写入 std::string完全便携。但是,如果您需要这样做,std::vector 可能仍然比使用原始 C 数组更可取。

* 虽然在 C++11 中这已经改变,所以 std::string 确实为您提供了一个连续的缓冲区

【讨论】:

@earlz:我认为调用函数 (std::string::c_str()) 相当琐碎。 std::string 使得很难获得可写缓冲区是一个问题——你必须依赖实现细节(这个问题被解释为标准是否真的需要 &s[0] 像处理向量一样工作),或者将另一个容器(向量或字符数组)与字符串混合。 使用 char* 而不是 string 的一个简单原因是,如果您必须避免动态内存分配(即,可能在中断处理程序中或在嵌入式系统上)。 我认为所有的例外都归结为,如果你知道你必须使用 char*。如果你没有充分的理由(而且大多数时候你没有),请使用 std::string。 另一个比 std::string 更喜欢 char* 的特定实例是您需要静态字符串常量的地方。如果有静态 std::string 实例(在 cpp 文件中或作为静态类成员),那么您可能会遇到初始化顺序问题。使用原始 char* 可以避免这个问题。【参考方案2】:

好的,自从我第一次回答以来,问题发生了很大变化。

与 std::string 相比,本机 char 数组是内存管理和缓冲区溢出的噩梦。我总是喜欢使用 std::string。

也就是说,由于性能限制(尽管 std::string 在某些情况下实际上可能更快——首先测量!)或在嵌入式环境中禁止使用动态内存,char 数组在某些情况下可能是更好的选择,等等

【讨论】:

【参考方案3】:

一般来说,std::string 是一种更干净、更安全的方法,因为它消除了程序员的内存管理负担。它比 char * 更快的主要原因是 std::string 存储了字符串的长度。因此,您不必在每次想要进行复制、追加等操作时遍历整个字符数组以查找终止 NULL 字符。

话虽如此,您仍然会发现许多混合使用 std::string 和 char * 的 c++ 程序,或者甚至从头开始编写自己的字符串类。在较旧的编译器中,std::string 是一种内存占用,并且不一定尽可能快。随着时间的推移,这种情况已经变得更好,但一些高性能应用程序(例如游戏和服务器)仍然可以从手动调整的字符串操作和内存管理中受益。

我建议从 std::string 开始,或者可能为它创建一个包含更多实用函数的包装器(例如,starts_with()、split()、format() 等)。如果您在对代码进行基准测试时发现字符串操作是一个瓶颈,或者使用了太多内存,那么您可以决定是否要接受额外的风险并进行自定义字符串库所需的测试。

提示:解决内存问题并仍然使用 std::string 的一种方法是使用嵌入式数据库,例如 SQLite。这在生成和操作非常大的字符串列表时特别有用,而且性能比您预期的要好。

【讨论】:

【参考方案4】:

C char * 字符串不能包含“\0”字符。 C++ 字符串可以毫无问题地处理空字符。如果用户输入包含 \0 的字符串而您使用 C 字符串,您的代码可能会失败。还有与此相关的安全问题。

【讨论】:

在 C 和 C++ 中,char * 字符串都指向常规内存数组。如果你真的想要,你可以在其中嵌入额外的 '\0' 字符。解析文件系统路径之类的东西时,一个常见的性能技巧是通过用 '\0' 交换字符来临时“截断”字符串。另一个常用的方法是用它来表示单个数组中的字符串列表,使用 '\0' 作为最后一个双 NULL 的分隔符。 更准确地说,C 字符串函数(strcpy 等)无法处理 '\0',因为它们将其用作字符串结束标记。如果你愿意编写自己的函数,char* 可以包含 '\0' 就可以了。 我从没见过有人使用'\0'这个角色 C 字符字符串只是指向原始内存的指针。至于字符'\0',我已经看到它用于在一个内存区域中有多个字符串的情况。你用两个'\0'结束多个字符串。原因是与多指针列表相比,它节省了内存。据说,很少见。【参考方案5】:

std::string 的实现对您隐藏了内存使用情况。如果您正在编写性能关键代码,或者您实际上不得不担心内存碎片,那么使用 char* 可以为您省去很多麻烦。

不过,对于其他任何事情,std::string 对您隐藏所有这些这一事实使其更加有用。

【讨论】:

【参考方案6】:

String 在性能方面实际上可能更好。还有无数其他原因——安全性、内存管理、方便的字符串函数,使 std::string 成为无限更好的选择。

编辑:要了解为什么字符串可能更有效,请阅读 Herb Sutter 的书籍 - 他讨论了一种在内部实现字符串以结合使用延迟初始化和引用的方法。

【讨论】:

如果您提供有关字符串如何更高效的更多信息,我会 +1。只是一个总结/结论,如果我想要详细信息,我可以去拿书。 我认为他指的是 Copy-on-Write 语义,由于多线程,如今这实际上是一个坏主意。 如果您指的是写时复制,那么时间已经证明是值得的,最后似乎写时复制不会提高当前早期复制实现的性能。写时复制在多线程方面存在许多问题,并且解决方案需要排他锁,其成本高于写时复制所提供的收益。 一些更有效的事情:获取字符串的大小是 std::string 中的常数时间操作,但使用 const char* 对字符串大小的线性时间,以及任何操作需要计算字符串的大小 @Charles, Dribeas, Caspin - 我指的是写时复制语义,我明白你的意思(尽管我必须权衡优点和缺点)。谢谢。【参考方案7】:

使用 std::string 是因为它非常方便 - 自动内存处理和方法/运算符。通过一些字符串操作,大多数实现都会进行适当的优化(例如延迟评估几个后续操作 - 节省内存复制)。

如果您需要依赖内存中的特定字符布局进行其他优化,请尝试使用std::vector<char>。如果你有一个非空向量vec,你可以使用&vec[0] 获得一个char* 指针(向量必须是非空的)。

【讨论】:

在这种情况下,我还建议显式包装字符串函数以处理 NULL 分隔符 - 仅设置字符向量不会对其进行分隔。【参考方案8】:

简短的回答,我不知道。例外情况是当我使用需要它们的第三方库时。在这些情况下,我会尝试坚持使用 std::string::c_str()。

【讨论】:

【参考方案9】:

在我所有的职业生涯中,我有机会在两个项目中使用 std::string。所有其他人都有自己的字符串类:)

话虽如此,对于新代码,我通常尽可能使用 std::string,除了模块边界(由 dll/共享库导出的函数),我倾向于公开 C 接口并远离 C++ 类型和问题编译器和标准库实现之间的二进制不兼容。

【讨论】:

大多数库已经开发了很长时间,并提供了自己的字符串函数。其中大多数(至少是维护良好的)提供了一种与 std::string 相互转换的方法。即使他们不这样做,使用 std::string 的 c_str() 函数在接口点转换为 c-string 也可能是一个好主意。我会冒昧地说,将每个供应商 API 包装在您自己的 API 中 - 使库之间的转换变得更容易,并远离过时的库(请阅读,那些甚至没有时间编写支持 std 的代码的人: :字符串)。【参考方案10】:

比较和对比以下 C 和 C++ 示例:

strlen(infinitelengthstring)

string.length()

【讨论】:

【参考方案11】:

std::string 几乎总是首选。即使为了速度,它在为更大的字符串动态分配更多之前在堆栈上使用小数组。

然而,在许多情况下仍需要 char* 指针将字符串/数据写入原始缓冲区(例如网络 I/O),而 std::string 无法做到这一点。

【讨论】:

您对std::string 在堆栈上使用小数组的评论不一定正确。这只是 一些 实现者可能使用的实现细节,而不是 std::string 的固有优势【参考方案12】:

我最近唯一一次在 C++ 程序中使用 C 风格的 char 字符串是在一个需要使用两个 C 库(当然)专门使用 C 字符串的项目中。在两种字符串类型之间来回转换让代码非常复杂。

我还必须对字符串进行一些操作,这对于 std::string 来说实际上有点尴尬,但如果没有上述约束,我不会认为这是使用 C 字符串的好理由。

【讨论】:

以上是关于为啥在 C++ 中更喜欢 char* 而不是字符串?的主要内容,如果未能解决你的问题,请参考以下文章

为啥将字符串作为文件名而不是 char* 传递时出现错误?

为啥打印 char* 会给出字符串而不是地址? [复制]

为啥 C 字符文字是整数而不是字符?

为啥 ostream::write() 在 C++ 中需要“const char_type*”而不是“const void*”?

C++中cout<<字符数组名;为啥能输出字符串?

为啥 putchar、toupper、tolow 等采用 int 而不是 char?