Nieco specjalne są tablice i wskaźniki znakowe. Związane jest to z faktem, że w klasycznym C nie ma typu napisowego, a rolę zmiennych napisowych pełnią tablice znaków, przy czym koniec napisu oznaczany jest znakiem ' \0'. Znak ten nazywany jest NUL; nie należy go mylić ze wskaźnikiem pustym NULL. Znak NUL to znak o kodzie ASCII równym zeru, który nie odpowiada żadnemu znakowi graficznemu. Zauważmy, że NULL (przez dwa 'L') jest zdefiniowaną nazwą preprocesora, której możemy używać w tekście programów (choć zaleca się stosować w jej miejsce liczbowego zera lub, w nowym standardzie, nullptr); NUL natomiast jest tradycyjną nazwą znaku zerowego ale nie jest zdefiniowaną nazwą preprocesora (makrem). Znak ten możemy wprowadzić do kodu programu używając literału ' \0' (z apostrofami).
Różne możliwości
definiowania tablic znakowych znajdujemy w następującym programie.
W linii 9 '
char tab1[] = "Kasia";'
tworzy tablicę sześciu (sic!) znaków na podstawie literału
napisowego: pięć znaków imienia i jako szósty znak '
\0', który
musi tam być, aby oznaczyć koniec napisu — kompilator doda ten
znak automatycznie.
1. #include <iostream> 2. using namespace std; 3. 4. void napisz (const char* tab) { 5. cout << "Napis: " << tab << endl; 6. } 7. 8. int main() { 9. char tab1[] = "Kasia"; 10. char tab2[] = {'B', 'a', 's', 'i', 'a', '\0'}; 11. const char *tab3 = "Wisia"; 12. cout << "Wymiar tab1: " << sizeof(tab1) << endl; 13. cout << "Wymiar tab2: " << sizeof(tab2) << endl; 14. cout << "Wymiar tab3: " << sizeof(tab3) << endl; 15. cout << "Wymiar \'Wisia\': "<< sizeof("Wisia")<< endl; 16. 17. tab1[0] = 'B'; 18. tab2[0] = 'K'; 19. //tab3[0] = 'C'; // ŹLE 20. 21. napisz(tab1); 22. napisz(tab2); 23. napisz(tab3); 24. }
Dlatego, jak widać z poniższego wydruku, wymiar tablicy tab1 jest 6:
Wymiar tab1: 6 Wymiar tab2: 6 Wymiar tab3: 8 Wymiar 'Wisia': 6 Napis: Basia Napis: Kasia Napis: WisiaTaki sam jest wymiar tablicy tab2, gdzie inicjalizacji dokonaliśmy sposobem analogicznym do tego, jaki znamy dla tablic innych typów: poprzez podanie wartości kolejnych elementów tablicy w nawiasie klamrowym. Tu znak ' \0' musieliśmy dodać „ręcznie”. W obu przypadkach powstały sześcioelementowe statyczne tablice znakowe, które, jak widać dalej w programie, można modyfikować.
Szczególna jest definicja zmiennej tab3. Inicjujemy tu zmienną typu const char* adresem literału napisowego. Jest to zmienna wskaźnikowa, więc jej wymiar wynosi 8 (lub 4 na maszynie 32-bitowej). Wydawałoby się, że odpowiada to przypadkowi pierwszemu (linia 9). Jest to jednak co innego. W linii 9 utworzyliśmy literał napisowy, następnie na stosie utworzona została tablica sześcioelementowa, do której przekopiowany został, znak po znaku, ten literał (wraz z kończącym znakiem ' \0'). Po przekopiowaniu tab1 jest normalną, modyfikowalną tablicą znaków o wymiarze 6.
Natomiast definiując tab3 utworzyliśmy trwały literał napisowy, nie na stosie, i przypisaliśmy adres początku tego napisu do zmiennej tab3. Sama tablica będzie utworzona w niemodyfikowalnym obszarze pamięci. Dlatego typ wskaźnika powinien być const char* a nie char* — o modyfikatorze const powiemy w następnym rozdziale. Zauważmy, że kompilator pozwoliłby nam zadeklarować typ tab3 jako char* (bez const), ale program i tak załamałby się przy próbie modyfikacji elementu wskazywanej tablicy (ta niekonsekwencja jest zaszłością historyczną).
Zauważmy też, że przekazując do funkcji napis w postaci tablicy znakowej nie musimy przekazywać jej wymiaru: obecność znaku ' \0' pozwala bowiem określić koniec napisu, a więc i jego długość.
Zwróćmy też uwagę na fakt, że wyjątkowe jest traktowanie zmiennych typu char* (lub const char*) przez operator wstawiania do strumienia. W zasadzie po 'cout « nap', gdzie nap jest typu char*, powinna zostać wypisana wartość zmiennej nap, czyli pewien adres (tak by było, gdyby zmienna nap była np. typu int*). Wskaźniki do zmiennych typu char są jednak traktowane odmiennie: zakłada się, że wskaźnik wskazuje na pierwszy znak napisu i wypisywane są wszystkie znaki od tego pierwszego poczynając aż do napotkania znaku ' \0'. Lepiej więc, żeby ten znak ' \0' tam rzeczywiście był!
Więcej na temat napisów w rozdziale im poświęconym .
T.R. Werner, 21 lutego 2016; 20:17