它与std::string有何不同?
7 回答
“字符串”实际上只是一个char
s 数组;以空字符结尾的字符串是一个空字符'\0'
标记字符串结尾(不一定是数组结尾)的字符串。代码中的所有字符串(由双引号分隔""
)都由编译器自动以空值结尾。
例如,"hi"
与 相同{'h', 'i', '\0'}
。
以空字符结尾的字符串是一个连续的字符序列,其中最后一个字符的二进制位模式全为零。我不确定您所说的“常用字符串”是什么意思,但如果您的意思是std::string
,那么 astd::string
不需要(直到 C++11)是连续的,也不需要有终止符。此外,astd::string
的字符串数据始终由std::string
包含它的对象分配和管理;对于以空字符结尾的字符串,没有这样的容器,您通常使用裸指针来引用和管理这样的字符串。
所有这些都应该包含在任何体面的 C++ 教科书中——我建议掌握Accelerated C++,这是其中最好的一本。
有两种主要的方式来表示一个字符串:
1) 以 ASCII 空 (nul) 字符 0 结尾的字符序列。您可以通过搜索终止符来判断它有多长。这称为空终止字符串,有时也称为空终止字符串。
2)一个字符序列,加上一个单独的字段(整数长度,或者指向字符串末尾的指针),告诉你它有多长。
我不确定“通常的字符串”,但经常发生的是,在谈论特定语言时,“字符串”一词用于表示该语言的标准表示。所以在 Java 中,java.lang.String 是一个类型 2 的字符串,所以这就是“字符串”的意思。在 C 语言中,“string”可能表示类型 1 字符串。为了准确起见,该标准非常冗长,但人们总是想省略“显而易见”的内容。
不幸的是,在 C++ 中,这两种类型都是标准的。std::string 是类型 2 字符串[*],但从 C 继承的标准库函数对类型 1 字符串进行操作。
[*] 实际上,std::string 通常被实现为一个字符数组,具有一个单独的长度字段和一个 nul 终止符。这样c_str()
就可以实现该功能,而无需复制或重新分配字符串数据。我不记得在不存储长度字段的情况下实现 std::string 是否合法:问题是标准需要什么样的复杂性保证。对于容器,一般size()
建议为 O(1),但实际上并不需要。因此,即使它是合法的,仅使用 nul 终止符的 std::string 实现也会令人惊讶。
'\0'
是带有代码 0、空终止符、空字符、NUL的 ASCII 字符。在C语言中,它用作用于表示字符串结尾的保留字符。许多标准函数,如 strcpy、strlen、strcmp 等都依赖于此。否则,如果没有NUL,则必须使用另一种表示字符串结束的方式:
这允许字符串是任意长度,只有一个字节的开销;存储计数的替代方法需要 255 的字符串长度限制或超过一个字节的开销。
来自维基百科
C++ std::string
遵循其他约定,其数据由一个名为 的结构表示_Rep
:
// _Rep: string representation
// Invariants:
// 1. String really contains _M_length + 1 characters: due to 21.3.4
// must be kept null-terminated.
// 2. _M_capacity >= _M_length
// Allocated memory is always (_M_capacity + 1) * sizeof(_CharT).
// 3. _M_refcount has three states:
// -1: leaked, one reference, no ref-copies allowed, non-const.
// 0: one reference, non-const.
// n>0: n + 1 references, operations require a lock, const.
// 4. All fields==0 is an empty string, given the extra storage
// beyond-the-end for a null terminator; thus, the shared
// empty string representation needs no constructor.
struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};
struct _Rep : _Rep_base
{
// Types:
typedef typename _Alloc::template rebind<char>::other _Raw_bytes_alloc;
// (Public) Data members:
// The maximum number of individual char_type elements of an
// individual string is determined by _S_max_size. This is the
// value that will be returned by max_size(). (Whereas npos
// is the maximum number of bytes the allocator can allocate.)
// If one was to divvy up the theoretical largest size string,
// with a terminating character and m _CharT elements, it'd
// look like this:
// npos = sizeof(_Rep) + (m * sizeof(_CharT)) + sizeof(_CharT)
// Solving for m:
// m = ((npos - sizeof(_Rep))/sizeof(CharT)) - 1
// In addition, this implementation quarters this amount.
static const size_type _S_max_size;
static const _CharT _S_terminal;
// The following storage is init'd to 0 by the linker, resulting
// (carefully) in an empty string with one reference.
static size_type _S_empty_rep_storage[];
static _Rep&
_S_empty_rep()
{
// NB: Mild hack to avoid strict-aliasing warnings. Note that
// _S_empty_rep_storage is never modified and the punning should
// be reasonably safe in this case.
void* __p = reinterpret_cast<void*>(&_S_empty_rep_storage);
return *reinterpret_cast<_Rep*>(__p);
}
bool
_M_is_leaked() const
{ return this->_M_refcount < 0; }
bool
_M_is_shared() const
{ return this->_M_refcount > 0; }
void
_M_set_leaked()
{ this->_M_refcount = -1; }
void
_M_set_sharable()
{ this->_M_refcount = 0; }
void
_M_set_length_and_sharable(size_type __n)
{
#ifndef _GLIBCXX_FULLY_DYNAMIC_STRING
if (__builtin_expect(this != &_S_empty_rep(), false))
#endif
{
this->_M_set_sharable(); // One reference.
this->_M_length = __n;
traits_type::assign(this->_M_refdata()[__n], _S_terminal);
// grrr. (per 21.3.4)
// You cannot leave those LWG people alone for a second.
}
}
_CharT*
_M_refdata() throw()
{ return reinterpret_cast<_CharT*>(this + 1); }
_CharT*
_M_grab(const _Alloc& __alloc1, const _Alloc& __alloc2)
{
return (!_M_is_leaked() && __alloc1 == __alloc2)
? _M_refcopy() : _M_clone(__alloc1);
}
// Create & Destroy
static _Rep*
_S_create(size_type, size_type, const _Alloc&);
void
_M_dispose(const _Alloc& __a)
{
#ifndef _GLIBCXX_FULLY_DYNAMIC_STRING
if (__builtin_expect(this != &_S_empty_rep(), false))
#endif
if (__gnu_cxx::__exchange_and_add_dispatch(&this->_M_refcount,
-1) <= 0)
_M_destroy(__a);
} // XXX MT
void
_M_destroy(const _Alloc&) throw();
_CharT*
_M_refcopy() throw()
{
#ifndef _GLIBCXX_FULLY_DYNAMIC_STRING
if (__builtin_expect(this != &_S_empty_rep(), false))
#endif
__gnu_cxx::__atomic_add_dispatch(&this->_M_refcount, 1);
return _M_refdata();
} // XXX MT
_CharT*
_M_clone(const _Alloc&, size_type __res = 0);
};
实际数据可能通过以下方式获得:
_Rep* _M_rep() const
{ return &((reinterpret_cast<_Rep*> (_M_data()))[-1]); }
此代码片段来自basic_string.h
我机器上位于的文件usr/include/c++/4.4/bits/basic_string.h
如您所见,差异很大。
以空字符结尾的字符串意味着字符串的结尾是通过出现空字符来定义的(所有位都为零)。
“其他字符串”例如必须存储自己的长度。
以空结尾的字符串是 C 中的本机字符串格式。例如,字符串文字被实现为以空结尾的字符串。结果,大量代码(首先是 C 运行时库)假定字符串是以空值结尾的。
以空结尾的字符串(c-string)是一个 char 数组,数组的最后一个元素是 0x0 值。std::string 本质上是一个向量,因为它是值的自动调整大小的容器。它不需要空终止符,因为它必须跟踪大小才能知道何时需要调整大小。
老实说,我更喜欢 c 字符串而不是标准字符串,它们只是在基本库中有更多的应用程序,那些代码和分配最少的应用程序,因此更难使用。