std :: string とはどう違いますか?
ヌル終了文字列は連続した文字列であり、最後の文字列はバイナリビットパターンがすべてゼロです。 「通常の文字列」の意味がわかりませんが、std::string
を意味する場合、std::string
は不要です( C++ 11まで )隣接している必要があり、ターミネータを持っている必要はありません。また、std::string
の文字列データは、それを含むstd::string
オブジェクトによって常に割り当てられ、管理されます。 nullで終わる文字列の場合、そのようなコンテナは存在せず、通常、ベアポインタを使用してそのような文字列を参照および管理します。
これらはすべて、まともなC++テキストブックで実際に取り上げる必要があります。最高の1つである Accelerated C++ を入手することをお勧めします。
「文字列」は、実際にはchar
sの単なる配列です。 nullで終わる文字列とは、null文字'\0'
が文字列の終わり(必ずしも配列の終わりではない)を示すものです。コード内のすべての文字列(二重引用符""
で区切られている)は、コンパイラによって自動的にヌルで終了します。
たとえば、"hi"
は{'h', 'i', '\0'}
と同じです。
文字列を表すには、主に2つの方法があります。
1)ASCII null(nul)文字、0、最後にある文字のシーケンス。ターミネータを検索することで、その長さを知ることができます。これはヌル終端と呼ばれます。文字列、または場合によってはヌル文字で終了します。
2)文字のシーケンスに加えて、長さを示す別のフィールド(整数の長さ、または文字列の末尾へのポインター)。
「通常の文字列」についてはわかりませんが、特定の言語について話すとき、「文字列」という言葉はその言語の標準表現を意味するために使用されることがよくあります。したがって、Javaでは、Java.lang.Stringはタイプ2の文字列であるため、「文字列」とはその意味です。 Cでは、「文字列」はおそらくタイプ1の文字列を意味します。標準は正確であるために非常に冗長ですが、人々は常に「明白な」ものを省きたいと思っています。
C++では、残念ながら両方のタイプが標準です。 std :: stringはタイプ2の文字列[*]ですが、Cから継承された標準ライブラリ関数はタイプ1の文字列を操作します。
[*]実際、std :: stringは多くの場合、文字の配列として実装され、個別の長さフィールドand nulターミネーターを持ちます。これにより、c_str()
関数を、文字列データをコピーまたは再割り当てする必要なく実装できます。長さフィールドを保存せずにstd :: stringを実装することが合法かどうかをすぐに思い出すことはできません。標準で要求される複雑さの保証は何ですか。一般的なコンテナの場合、size()
はO(1)であることが推奨されますが、実際には必須ではありません。したがって、たとえそれが正当であっても、nul-terminatorsを使用するだけのstd :: stringの実装は驚くでしょう。
'\0'
ASCIIコード0の文字、ヌルターミネータ、ヌル文字、[〜#〜] nul [〜#〜]。[〜#〜] c [〜#〜]言語では、文字列の終わりを示すために使用される予約文字として機能します。 strcpy、strlen、strcmpなどの標準関数はこれに依存しますが、そうでない場合は[〜#〜] nul [〜#〜] 、文字列の終わりを知らせる別の方法を使用する必要があります。
これにより、1バイトのオーバーヘッドのみで、文字列を任意の長さにすることができます。カウントを保存する代わりに、255の文字列長制限または1バイト以上のオーバーヘッドが必要です。
from wikipedia
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ランタイムライブラリ)は、文字列がnullで終了していると想定しています。
Nullで終了する文字列(c-string)はcharの配列であり、配列の最後の要素は0x0値です。 std :: stringは、値の自動サイズ変更コンテナであるという点で、本質的にベクトルです。サイズ変更の必要性を知るためにサイズを追跡する必要があるため、nullターミネータは必要ありません。
正直なところ、私は標準文字列よりもC文字列の方が好きです。基本ライブラリにはより多くのアプリケーションがあり、コードと割り当てが最小限であり、そのため使用が難しくなっています。