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: Wisia
Taki 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