C/Napisy: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
Linia 5:
{{Uwaga|Napisy w języku C mogą być przyczyną wielu trudnych do wykrycia błędów w programach. Warto dobrze zrozumieć, jak należy operować na łańcuchach znaków i zachować szczególną ostrożność w tych miejscach, gdzie napisów używamy.}}
 
That alone wwas an egregious oversight on thheir own part, since cfgegeaebdgedakb
==Łańcuchy znaków w języku C==
Napis jest zapisywany w kodzie programu jako ciąg znaków zawarty pomiędzy dwoma cudzysłowami.
 
<source lang="c">
printf ("Napis w języku C");
</source>
 
W pamięci taki łańcuch jest następującym po sobie ciągiem znaków (char), który kończy się znakiem "null" (czyli po prostu liczbą zero), zapisywanym jako '\0'.
 
Jeśli mamy napis, do poszczególnych znaków odwołujemy się jak w tablicy:
 
<source lang="c">
const char *tekst = "Jakiś tam tekst";
printf("%c\n", "przykład"[0]); /* wypisze p - znaki w napisach są numerowane od zera */
printf("%c\n", tekst[2]); /* wypisze k */
</source>
 
Ponieważ napis w pamięci kończy się zerem umieszczonym tuż za jego zawartością, odwołanie się do znaku o indeksie równym długości napisu zwróci zero:
 
<source lang="c">
printf("%d", "test"[4]); /* wypisze 0 */
</source>
 
Napisy możemy wczytywać z klawiatury i wypisywać na ekran przy pomocy dobrze znanych funkcji [[C/scanf|scanf]], [[C/printf|printf]] i pokrewnych. Formatem używanym dla napisów jest %s.
 
<source lang="c">
printf("%s", tekst);
</source>
 
Większość funkcji działających na napisach znajduje się w pliku nagłówkowym [[C/Biblioteka standardowa/Indeks tematyczny#string.h|string.h]].
 
 
Jeśli łańcuch jest zbyt długi, można zapisać go w kilku linijkach, ale wtedy przechodząc do następnej linii musimy na końcu postawić znak "\".
 
<source lang="c">
printf("Ten napis zajmuje \
więcej niż jedną linię");
</source> <!-- nie zmieniać wcięcia! kod ma zajmować dwie linijki -->
 
Instrukcja taka wydrukuje:
Ten napis zajmuje więcej niż jedną linię
 
Możemy zauważyć, że napis, który w programie zajął więcej niż jedną linię, na ekranie zajął tylko jedną. Jest tak, ponieważ "\" informuje kompilator, że łańcuch będzie kontynuowany w następnej linii kodu - nie ma wpływu na prezentację łańcucha. Aby wydrukować napis w kilku liniach należy wstawić do niego <tt>\n</tt> ("n" pochodzi tu od "new line", czyli "nowa linia").
 
<source lang="c">
printf("Ten napis\nna ekranie\nzajmie więcej niż jedną linię.");
</source>
 
W wyniku otrzymamy:
 
Ten napis
na ekranie
zajmie więcej niż jedną linię.
 
=== Jak komputer przechowuje w pamięci łańcuch? ===
[[Grafika:Merkkijono.svg|thumb|300px|Napis "Merkkijono" przechowywany w pamięci]]
Zmienna, która przechowuje łańcuch znaków, jest tak naprawdę wskaźnikiem do ciągu znaków (bajtów) w pamięci. Możemy też myśleć o napisie jako o tablicy znaków (jak wyjaśnialiśmy wcześniej, [[C/Wskaźniki#Tablice to też wskaźniki|tablice to też wskaźniki]]).
 
{{clear}}
Możemy wygodnie zadeklarować napis:
 
<source lang="c">
const char *tekst = "Jakiś tam tekst"; /* Umieszcza napis w obszarze danych programu i przypisuje adres */
char tekst[] = "Jakiś tam tekst"; /* Umieszcza napis w tablicy */
char tekst[] = {'J','a','k','i','s',' ','t','a','m',' ','t','e','k','s','t','\0'};
/* Tekst to taka tablica jak każda inna */
</source>
 
Kompilator automatycznie przydziela wtedy odpowiednią ilość pamięci (tyle bajtów, ile jest liter plus jeden dla kończącego nulla). Jeśli natomiast wiemy, że dany łańcuch powinien przechowywać określoną ilość znaków (nawet, jeśli w deklaracji tego łańcucha podajemy mniej znaków) deklarujemy go w taki sam sposób, jak tablicę jednowymiarową:
 
<source lang="c">
char tekst[80] = "Ten tekst musi być krótszy niż 80 znaków";
</source>
 
Należy cały czas pamiętać, że napis jest tak naprawdę tablicą. Jeśli zarezerwowaliśmy dla napisu 80 znaków, to przypisanie do niego dłuższego napisu spowoduje '''pisanie po pamięci'''.
 
{{uwaga|1=Deklaracja <tt>const char *tekst = "cokolwiek";</tt> oraz <tt>char tekst[] = "cokolwiek";</tt> pomimo, że wyglądają bardzo podobnie bardzo się od siebie różnią. W przypadku pierwszej deklaracji próba zmodyfikowania napisu (np. <tt>tekst[0] = 'C';</tt>) może wyświetlać błąd kompilacji. Dzieje się tak dlatego, że <tt>const char *tekst = "cokolwiek";</tt> deklaruje wskaźnik na '''stały''' obszar pamięci{{r|const}}.}}
 
Pisanie po pamięci może czasami skończyć się błędem dostępu do pamięci ("segmentation fault" w systemach UNIX) i zamknięciem programu, jednak może zdarzyć się jeszcze gorsza ewentualność - możemy zmienić w ten sposób przypadkowo wartość innych zmiennych. Program zacznie wtedy zachowywać się nieprzewidywalnie - zmienne a nawet stałe, co do których zakładaliśmy, że ich wartość będzie ściśle ustalona, mogą przyjąć taką wartość, jaka absolutnie nie powinna mieć miejsca.
 
Kluczowy jest też kończący napis znak null. W zasadzie wszystkie funkcje operujące na napisach opierają właśnie na nim. Na przykład, strlen szuka rozmiaru napisu idąc od początku i zliczając znaki, aż nie natrafi na znak o kodzie zero. Jeśli nasz napis nie kończy się znakiem null, funkcja będzie szła dalej po pamięci. Na szczęście, wszystkie operacje podstawienia typu tekst = "Tekst" powodują zakończenie napisu nullem (o ile jest na niego miejsce) {{r|null}}.
 
=== Znaki specjalne ===
Jak zapewne zauważyłeś w poprzednim przykładzie, w łańcuchu ostatnim znakiem jest znak o wartości zero ('\0'). Jednak łańcuchy mogą zawierać inne znaki specjalne(sekwencje sterujące), np.:
* <nowiki>'\a' - alarm (sygnał akustyczny terminala)</nowiki>
* <nowiki>'\b' - backspace (usuwa poprzedzający znak) </nowiki>
* <nowiki>'\f' - wysuniecie strony (np. w drukarce)</nowiki>
* <nowiki>'\r' - powrót kursora (karetki) do początku wiersza</nowiki>
* <nowiki>'\n' - znak nowego wiersza</nowiki>
* <nowiki>'\"' - cudzysłów</nowiki>
* <nowiki>'\'' - apostrof</nowiki>
* <nowiki>'\\' - ukośnik wsteczny (backslash)</nowiki>
* <nowiki>'\t' - tabulacja pozioma</nowiki>
* <nowiki>'\v' - tabulacja pionowa</nowiki>
* <nowiki>'\?' - znak zapytania (pytajnik)</nowiki>
* <nowiki>'\ooo' - liczba zapisana w systemie oktalnym (ósemkowym), gdzie 'ooo' należy zastąpić trzycyfrową liczbą w tym systemie</nowiki>
* <nowiki>'\xhh' - liczba zapisana w systemie heksadecymalnym (szesnastkowym), gdzie 'hh' należy zastąpić dwucyfrową liczbą w tym systemie</nowiki>
* <nowiki>'\unnnn' - uniwersalna nazwa znaku, gdzie 'nnnn' należy zastąpić czterocyfrowym identyfikatorem znaku w systemie szesnatkowym. 'nnnn' odpowiada dłuższej formie w postaci '0000nnnn'</nowiki>
* <nowiki>'\unnnnnnnn' - uniwersalna nazwa znaku, gdzie 'nnnnnnnn' należy zastąpić ośmiocyfrowym identyfikatorem znaku w systemie szesnatkowym. </nowiki>
 
Warto zaznaczyć, że znak nowej linii ('\n') jest w różny sposób przechowywany w różnych systemach operacyjnych. Wiąże się to z pewnymi historycznymi uwarunkowaniami. W niektórych systemach używa się do tego jednego znaku o kodzie 0x0A (Line Feed - nowa linia). Do tej rodziny zaliczamy systemy z rodziny Unix: Linux, *BSD, Mac OS X inne. Drugą konwencją jest zapisywanie '\n' za pomocą dwóch znaków: LF (Line Feed) + CR (Carriage return - powrót karetki). Znak CR reprezentowany jest przez wartość 0x0D. Kombinacji tych dwóch znaków używają m.in.: CP/M, DOS, OS/2, Microsoft Windows. Trzecia grupa systemów używa do tego celu samego znaku CR. Są to systemy działające na komputerach Commodore, Apple II oraz Mac OS do wersji 9. W związku z tym plik utworzony w systemie Linux może prezentować się inaczej pod systemem Windows.
 
==Operacje na łańcuchach==