什么是以null结尾的字符串?

时间:2010-01-10 14:04:47

标签: c++ null-terminated

它与std::string的区别如何?

7 个答案:

答案 0 :(得分:66)

“string”实际上只是char s的数组;以null结尾的字符串是一个空字符'\0'标记字符串结尾的字符串(不一定是数组的结尾)。代码中的所有字符串(由双引号""分隔)都由编译器自动终止。

例如,"hi"{'h', 'i', '\0'}相同。

答案 1 :(得分:41)

以null结尾的字符串是连续的字符序列,最后一个字符的二进制位模式全为零。我不确定你的“常用字符串”是什么意思,但如果你的意思是std::string,那么std::string不是必需的(until C++11)是连续的,并且不是必需的有一个终结者。此外,std::string的字符串数据始终由包含它的std::string对象分配和管理;对于以null结尾的字符串,没有这样的容器,您通常使用裸指针来引用和管理这些字符串。

所有这些都应该包含在任何体面的C ++教科书中 - 我建议抓住Accelerated C++,其中最好的一本。

答案 2 :(得分:16)

表示字符串有两种主要方式:

1)结尾处带有ASCII null(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-terminators的std :: string实现也会令人惊讶。

答案 3 :(得分:6)

'\0' 

是一个ASCII字符,代码为0,空终止符,空字符, NUL 。在 C 语言中,它用作表示字符串结尾的保留字符。许多标准函数,如strcpy,strlen,strcmp等都依赖于此。否则,如果没有 NUL ,则必须使用另一种表示字符串结尾的方法:

  

这允许字符串为任何长度,只有一个开销   字节;存储计数的替代方法需要一个字符串   长度限制为255或开销超过一个字节。

     

来自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

正如您所看到的,差异很大。

答案 4 :(得分:3)

以null结尾的字符串意味着字符串的结尾是通过出现null-char来定义的(所有位都为零)。

“其他字符串”,例如必须存放自己的长度。

答案 5 :(得分:1)

以null结尾的字符串是C中的本机字符串格式。例如,字符串文字实现为以null结尾。因此,大量代码(以C开头的C运行时库)假定字符串以空值终止。

答案 6 :(得分:0)

空终止字符串(c-string)是char的数组,数组的最后一个元素是0x0值。 std :: string本质上是一个向量,因为它是值的自动调整大小容器。它不需要空终止符,因为它必须跟踪大小以了解何时需要调整大小。

老实说,我更喜欢c-strings而不是std,他们在基本库中只有更多的应用程序,代码和分配最少的应用程序,并且因此更难使用。