【问题标题】:Why do data() and c_str() return char const*, while operator[] returns char&?为什么data()和c_str()返回char const*,而operator[]返回char&?
【发布时间】:2013-11-14 10:49:34
【问题描述】:

为什么std::string::datastd::string::c_str() 返回指向const 字符的指针,而std::string::operator[] 返回指向可变 字符的引用?

std::string string("eightfold is the greatest");

auto s = string.data();
*s = 'r'; // illegal

auto t = &string[0];
*t = 'r'; // totally fine

auto& c = string[0];
c = 'r'; // totally fine

为什么std::string::data()std::string::c_str()不返回char*,或者std::string::operator[]为什么不返回char const&

这背后的原理是什么?

【问题讨论】:

  • 请注意&s[0]返回的char*不能被视为以null结尾的字符串,而c_str()返回的char const*是以null结尾的字符串。
  • @Nawaz,我几乎可以肯定 null 保证在 C++11 中存在(并且它是连续的),即使您无法修改它。
  • @Nawaz:在 C++11 中,&s[0] 保证是 C 字符串。在 C++11 之前的语言版本中,不能保证。
  • @AndreyT:是的,我记得。谢谢提醒。

标签: c++ string constants design-rationale design-choices


【解决方案1】:

operator [] 让您可以直接访问std::string 对象的受控序列。 c_str()原来没有。

std::string 的原始规范中,存储的序列不需要是一个以零结尾的字符串。这意味着在一般情况下c_str() 无法返回指向存储序列的直接指针。它必须返回一个指针,指向一个完全独立的、单独分配的受控序列的临时副本(添加一个零终止符)。因此,尝试修改 c_str() 返回的 C 字符串完全没有意义。应用于该单独 C 字符串的任何修改都不会传播到实际的受控序列。 (事实上​​,规范明确禁止任何修改尝试。例如,对于空的std::string,实现可以简单地返回指向字符串文字"" 的指针,这当然是不可修改的,并且可以很容易地在所有人之间共享std::string 对象。)所以,让c_str() 返回const char * 是非常有意义的。

C++11 更改了 c_str() 的内部规范,使其返回指向实际受控序列的直接指针。但是c_str() 的外部规范保持不变,以使其与旧规范保持一致。

【讨论】:

  • 不,它不必是“完全独立、单独分配的受控序列的临时副本”。可能会简单地附加一个 NUL(但不计入size(),您将(严格)使用size() < capacity() 而不是通常的size() <= capacity()
  • @Ben Voigt:我的“它有……”这句话是为了延续上一句:“一般情况下……”。是的,众所周知,现实生活中的实现更喜欢显式存储零终止符并实现轻量级c_str()。然而,在一般情况下,“抽象”实现会分配一个独立的缓冲区。 c_str() 的整个原始规范是特意编写的以支持这种方法。 IE。无论c_str() 实际返回什么,抽象程序都必须假定缓冲区是独立分配的。
【解决方案2】:

由于历史原因,C++ 及其标准库支持 C-strings(字符数组),并且许多 C++ 代码使用 C-strings 进行输入和输出。

您还可以想象 std::string 的一种可能实现,它将其数据保存在字符数组中。这通常是完全私有的实现细节,不会通过类的公共接口公开。

编辑:明确地说,一个类通常不会公开其私有数据的非常量视图。要了解为什么这会成为问题,请想象以下代码:

std::string s("abc");  
char* ps = s.c_str();  //  ps[0] == 'a' and ps[3] == '\0'
ps[3] = 'd';  // string is not null terminated
printf("%s", s.c_str());  // printing non-terminated string.

此类更改将允许该类的用户以破坏不变量的方式更改其私有数据,即以下不变量:“用于存储的字符缓冲区将以空值终止。”

operator[] 的部分约定是调用者不得提供大于或等于字符串长度的参数。 at(size_t pos) member function 通过抛出异常来强制进行边界检查。 std::string::operator[] 仍然可以不安全地使用,但是至少可以使用 document a contract,这与 ps[3] 中的指针取消引用运算符不同。

编辑结束

但为了支持与需要 const char* C 字符串的函数的互操作性,std::string 会公开此字符缓冲区。

当然,与std::vector 一样,用户可能希望修改字符串中的单个元素(字符),这就是字符串提供operator[] 的原因。

(实际上,string 实现通常有一个固定长度的字符缓冲区,它们在内部保留,然后如果字符串的内容超过该固定长度,则在堆上“重新分配”。这称为“小字符串优化。 ")

为什么会有data() 成员函数,你可能会问,当有一个完美可用的c_str() 成员函数时?我认为这是为了简化泛型编程:std::arraystd::vector 也有 data() 成员函数,而 std::strings 被设计为像容器一样工作。

【讨论】:

  • “但是为了支持与需要 const char* C 字符串的函数的互操作性,std::string 公开了这个字符缓冲区。” char* 不能隐式转换为 char const* 吗?
  • 这个答案完全错过了这个问题。这不是关于为什么一个c_str()函数,而是为什么它返回一个char const*而不是char*
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-10
  • 2021-04-02
  • 1970-01-01
  • 2012-05-26
  • 1970-01-01
  • 2014-02-12
  • 1970-01-01
相关资源
最近更新 更多