2010-01-10 11 views

Odpowiedz

36

Łańcuch zakończony znakiem NUL jest ciągiem ciągłym znaków, z których ostatni ma binarny wzorzec bitowy zawierający wszystkie zera. Nie jestem pewien, co masz na myśli przez "zwykły ciąg znaków", ale jeśli masz na myśli std::string, to nie jest wymagane std::string (until C++11), aby być przyległym i nie musi mieć terminatora. Ponadto dane ciągów std::string są zawsze przydzielane i zarządzane przez obiekt std::string, który je zawiera; dla łańcucha zakończonego zerem, nie ma takiego kontenera, a Ty zazwyczaj odwołujesz się do tych łańcuchów i zarządzasz nimi za pomocą gołych wskaźników.

Wszystko to powinno być opisane w przyzwoitym podręczniku tekstowym w języku C++ - polecam uzyskanie Accelerated C++, jednego z najlepszych z nich.

+4

Zasadniczo, wyzerowany bajt określa długość łańcucha znaków w C. – Costique

+0

To proste, thx! – lhj7362

+0

Ostatni znak nie musi mieć wzorca bitowego wszystkich zer, musi on mieć tylko * wartość * 0. – avakar

2

Łańcuch zakończony znakiem NUL oznacza, że ​​koniec łańcucha jest definiowany przez wystąpienie znaku null (wszystkie bity są zerowe).

"Inne ciągi" np. muszą przechowywać swoją własną długość.

46

"Ciąg" jest tak naprawdę tylko tablicą char s; łańcuch zakończony zerem to taki, w którym pusty znak '\0' oznacza koniec ciągu (niekoniecznie koniec tablicy). Wszystkie ciągi w kodzie (ograniczone przez podwójne cudzysłowy "") są automatycznie kończone przez kompilator.

Na przykład "hi" jest taki sam jak {'h', 'i', '\0'}.

+4

Lepiej zrozumieć, niż zaakceptowana odpowiedź. +1 – Mike

+1

Warto wspomnieć, że kompilatory szukają znaku pustego, aby określić długość łańcucha. –

+0

Mam stałą łańcuchową i zapisałem a, b, c jako temp [0], temp [1] i temp [2]. Teraz, gdy robię "cout << temp", to nie daje - "abc". Co powinienem zrobić? Dowiedziałem się, że '\ 0' również nie działa tutaj jako terminator znaków. –

1

Łańcuch zakończony znakiem NUL jest rodzimym formatem ciągu w C. Literały łańcuchowe na przykład są zaimplementowane jako zakończone znakiem NUL. W wyniku tego cała masa kodu (początkowo biblioteka C-run-time) zakłada, że ​​łańcuchy są zakończone znakiem NUL.

14

Istnieją dwa główne sposoby reprezentujące ciąg:

1) sekwencję znaków ASCII posiadających wartość null (nul) charakteru, 0, na końcu. Możesz sprawdzić, jak długo trwa wyszukiwanie terminatora. Nazywane jest to łańcuchem zakończonym znakiem NUL, lub czasem zakończone nul.

2) Sekwencja znaków plus oddzielne pole (długość całkowita lub wskaźnik na końcu łańcucha), aby poinformować, jak długo to trwa.

Nie jestem pewien co do "zwykłego łańcucha", ale często zdarza się, że mówiąc o danym języku, słowo "łańcuch" oznacza standardową reprezentację tego języka. Tak więc w Javie plik java.lang.String jest ciągiem typu 2, więc to właśnie oznacza "ciąg znaków". W C, "ciąg" prawdopodobnie oznacza ciąg typu 1. Standard jest dość gadatliwy, aby być precyzyjnym, ale ludzie zawsze chcą pominąć to, co "oczywiste".

Niestety w C++ oba typy są standardowe. std :: string jest łańcuchem typu 2 [*], ale standardowe funkcje biblioteczne odziedziczone po C działają na łańcuchach typu 1.

[*] Właściwie std :: string jest często implementowany jako tablica znaków, z osobnym polem długości i terminatorem nul. Dzieje się tak dlatego, że funkcja c_str() może zostać zaimplementowana bez konieczności kopiowania lub ponownego przydzielania danych ciągu. Nie pamiętam od ręki, czy legalne jest wdrażanie std :: string bez przechowywania pola długości: pytanie, jakie wymagania gwarantuje standard złożoności. Ogólnie dla kontenerów size() zaleca się mieć wartość O (1), ale nie jest to wymagane.Więc nawet jeśli jest to legalne, implementacja std :: string, która po prostu używa nul-terminatorów byłaby zaskakująca.

6
'\0' 

jest znak ASCII o kodzie 0, null, null terminatora charakteru, NUL. W języku C służy jako zarezerwowana litera, która służy do oznaczenia końca ciągu znaków. Wiele standardowych funkcji, takich jak strcpy, strlen, strcmp i inne, polega na tym. W przeciwnym razie, jeśli nie było NUL, inny sposób zasygnalizować koniec łańcucha musi zostały wykorzystane:

Pozwala to ciąg być dowolnej długości tylko obciążania jednej bajt; alternatywa zapisywania zliczeń wymaga albo ograniczenia długości linii o wartości 255, albo o wiele więcej niż jednego bajtu.

z wikipedia

C++std::string wynika to innych konwencji, a jej dane są reprezentowane przez strukturę zwaną _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); 
     }; 

rzeczywistych danych może być uzyskana z:

_Rep* _M_rep() const 
     { return &((reinterpret_cast<_Rep*> (_M_data()))[-1]); } 

ten fragment kodu pochodzi z pliku basic_string.h który na moim komputerze znajduje sie w usr/include/c++/4.4/bits/basic_string.h

Więc jak widać, różnica jest znacząca.

0

Łańcuch znaków zakończony znakiem zerowym (ciąg-c) jest tablicą znaków, a ostatni element tablicy jest wartością 0x0. Std :: string jest w zasadzie wektorem, ponieważ jest to automatyczny rozmiar kontenera dla wartości. Nie potrzebuje terminatora zerowego, ponieważ musi śledzić rozmiar, aby wiedzieć, kiedy wymagana jest zmiana rozmiaru.

Szczerze mówiąc, wolę ciągi c-owe niż standardowe, mają po prostu więcej aplikacji w podstawowych bibliotekach, te z minimalnym kodem i przydziałami, a tym trudniejsze w użyciu.