Łańcuchy znaków

Spis treści Poprzednia strona: Wyliczenia Następna strona: Data i czas

Łańcuch znaków, tzw. string albo tekst to kolejne znaki w standardzie Unicode w pliku źródłowym C#, ograniczone od początku i od końca przez cudzysłowy (podwójne, górne, „angielskie”, czyli znaki \x22 a nie „polskie” czyli lewy \u201E i prawy \u201D). Łańcuch znaków jest literałem, czyli ma wartość. Wartość ta jest typu string, a konkretnie System.String. Słowo string jest słowem kluczowym. Typ string jest typem referencyjnym, czyli zmienne typu string, tzw. instancje typu, są obiektami. Jednak w porównaniach zachowuje się on tak, jak typ wartościowy, czyli porównywane są wartości, a nie referencje.

Stringi w C# są podobne do stringów z języka C i C++, ponieważ obsługują tzw. sekwencje ucieczki [escape sequences]. Są to ciągi znaków o specjalnym znaczeniu i najczęściej jedna cała taka sekwencja odpowiada pojedynczemu znakowi. Sekwencja ucieczki zaczyna się zawsze od znaku odwrotnego ukośnika \ [backslash]. Kiedy kompilator napotka \ w ciągu znaków, to traktuje to jako początek sekwencji ucieczki, a nie jako \ znak. Żeby uzyskać pojedynczy \ znak trzeba napisać go tak \\, czyli podwójnie. Znak \ często występuje w ścieżkach do plików na dysku, bo w systemie Windows oddziela od siebie nazwy folderów i plików. Żeby nie było potrzeby zmiany w ścieżkach plików każdego znaku \ na podwójny \\ (chociaż nic nie stoi na przeszkodzie, aby to robić, ale to kłopotliwe), można wyłączyć interpretowanie sekwencji ucieczki przez poprzedzenie stringa znakiem @ (bezpośrednio przed pierwszym cudzysłowem). Taki łańcuch znaków to tzw. dosłowny string [verbatim string] i uwzględniane są wszystkie znaki dokładnie tak, jak widać je w pliku (nawet nowe linie!). W takim stringu wszystkie znaki, które występują między cudzysłowami są w bezpośredni sposób częścią tego stringu. Jedynym wyjątkiem jest znak cudzysłowu, który nie może sam wystąpić wewnątrz stringu, bo to by oznaczało koniec stringu w miejscu jego wystąpienia. Aby wprowadzić do takiego stringu cudzysłów nie pomoże sekwencja ucieczki \”, która działała w zwykłych stringach. W stringach dosłownych wstawiamy pojedynczy cudzysłów poprzez podwójny znak cudzysłowu „”. Skoro możliwe jest pisanie w plikach takich dosłownych stringów, to znaczy, że całe pliki źródłowe C# są w standardzie Unicode. Faktycznie specyfikacja języka C# nie wymaga, ale tylko zaleca używanie UTF-8 jako standardu kodowania kodu źródłowego plików C#. Czyli można mieć nazwy zmiennych z polskimi znakami (np.: współczynnik), a nawet z chińskimi, jak ktoś lubi. Dla zainteresowanych chiński znak oznaczający miłość, to U+611B (są jeszcze inne, bo to tylko jeden z rodzajów miłości) i w C# uzyskamy go tak \u611B, tak \x611B albo tak \U0000611B. Symbol serduszka (oznaczenie koloru kier w kartach) to code point U+2665 i znak \u2665 rzeczywiście tak się na konsoli wyświetla.

Nie każda sekwencja ucieczki ma sens. Zdefiniowane są tylko takie:

\’ apostrof \u0027 – początek i koniec literału znakowego;
\” cudzysłów \u0022 – początek i koniec literału łańcuchowego;
\\ lewy ukośnik \x5C – początek sekwencji ucieczki, znak oddzielający nazwy katalogów i plików w ścieżce;
\0 znak o kodzie zero \x0 – w języku C był zwykle ostatnim znakiem łańcucha znaków, w C# raczej nieużywany;
\a alarm \x7 – wypisany na konsoli powinien wydać dźwięk z głośniczka;
\b backspace \x8 – usuwa poprzedni znak;
\t tabulator poziomy \x9 – przesuwa kursor w prawo o określoną (albo zależną od pozycji poziomej) liczbę spacji;
\r powrót karetki \u000D – przenosi kursor na początek bieżącej linii;
\n nowa linia \u000A – przenosi kursor do następnej linii, w systemie Windows zwykle poprzedza go znak \r;
\f form feed \u000C – przesunięcie drukarki znakowej na początek następnej strony, dziś rzadko używany, bo drukarki są zwykle postscriptowe;
\v tabulator pionowy \u000B – przechodzi do następnej pozycji tabulacyjnej, ale w pionie;
\u rozpoczyna sekwencję dokładnie 4 cyfr szesnastkowych oznaczających numer znaku Unicode;
\U rozpoczyna sekwencję dokładnie 8 cyfr szesnastkowych oznaczających numer znaku Unicode z zakresu od U+100000 do U+10FFFF;
\x rozpoczyna sekwencję co najmniej jednej i co najwyżej 4 cyfr szesnastkowych oznaczających numer znaku Unicode;

Rada: Nie ufaj \v \b \a ani \f i nie spodziewaj się po nich jakiegokolwiek sensownego działania. Te znaki należą do przeszłości.
Uwaga: Nie zdziw się, jeśli gdzieś pojedynczy \n zadziała jak \r\n, a pojedynczy \r zadziała jak \n. To jest Windows!

Console.WriteLine("kasia\rb"); // u mnie wypisuje basia, bo \r cofa kursor na początek i k jest nadpisane przez b
Console.WriteLine("k\basia"); // u mnie wypisuje asia, bo \b usuwa k
Console.WriteLine("\f\v"); // string zawiera znaki o kodach 12 i 11, a u mnie wypisuje dwa inne znaki
Console.WriteLine("\u2640\u2642"); // wypisuje te same znaki, ale to jest inny string jak powyżej, bo zawiera znaki o kodach 9792 i 9794
Console.WriteLine("♀♂"); // wypisuje to samo i to jest taki sam string jak powyżej, znaki Unicode są zawarte bezpośrednio
Console.WriteLine("\"\""); // dwa cudzysłowy
Console.WriteLine(@""""""); // dwa cudzysłowy ale verbatim
Console.WriteLine("\u201e\u201d"); // polskie cudzysłowy u mnie wyglądają na konsoli tak samo jak angielskie

Zaznaczam: tak to wygląda u mnie! U Ciebie i u Twojego pracodawcy może być zupełnie inaczej. Generalnie znak specjalny albo wygląda, albo działa i jeśli wygląda, to znaczy że nie działa, a używana czcionka podstawiła jakiś inny znak jako jego wygląd. Standardowo nieznane znaki powinny wyglądać tak, jak znak zapytania ?, ale to zależy od tego kto i jak zrobił daną czcionkę i algorytm korzystania z niej w danym przypadku (można spotkać też biały lub pusty prostokąt w roli nieznanego znaku).

W C# mamy typ char, który ma zawsze 2 bajty. Łańcuchy znaków przechowują wiele znaków. C# obsługuje standard Unicode i zapisuje znaki w UTF-16 (na 16-bitach, czyli 2 bajtach). Znaki Unicode, tzw. code point-y, oznaczamy tak U+xxxxxx, gdzie xxxxxx to cyfry szestastkowe 0123456789ABCDEF. Zdefiniowano tylko znaki w zakresie od U+000000 do U+10FFFF. Generalnie Unicode jest systemem 4-bajtowym, więc niektóre znaki Unicode (te większe od U+FFFF) potrzebują do zapisu dwóch znaków w C#, czyli dwóch zmiennych typu char. Jest to wtedy specjalna para znaków, tzw. surrogate pair, gdzie pierwszy [lead] to tzw. high surrogate z zakresu od U+D800 do U+DBFF, a drugi [trail] to tzw. low surrogate z zakresu od U+DC00 do U+DFFF. Takie znaki muszą w łańcuchu wystąpić parami i w odpowiedniej kolejności, w przeciwnym razie są błędne. Najmniejszy z tych rozszerzonych znaków to U+10000, a największy to U+10FFFF. Aby wprowadzić taki znak w C# używamy sekwencji ucieczki \U. Ponieważ wymaga ona 8 cyfr szesnastkowych to w stringu pierwsze dwie zawsze są zerami, a w stałej znakowej pierwsze cztery zawsze są zerami. Popatrzmy na poniższy przykład:

Console.WriteLine("\U00000010".Length); // wypisze 1
Console.WriteLine("\U0000FFFF".Length); // wypisze 1
Console.WriteLine("\U00100000".Length); // wypisze 2
Console.WriteLine("\U0010FFFF".Length); // wypisze 2
Console.WriteLine("{0:X4}{1:X4}", (int)"\U00100000"[0], (int)"\U00100000"[1]);  // wypisze DBC0DC00
Console.WriteLine("{0:X4}{1:X4}", (int)"\U0010FFFF"[0], (int)"\U0010FFFF"[1]);  // wypisze DBFFDFFF
Console.WriteLine(Char.IsHighSurrogate("\U00100000"[0])); // wypisze True
Console.WriteLine(Char.IsLowSurrogate("\U00100000"[1])); // wypisze True
Console.WriteLine(Char.IsSurrogatePair("\U00100000"[0], "\U00100000"[1])); // wypisze True
Console.WriteLine(Char.IsSurrogatePair("\U00100000"[1], "\U00100000"[0])); // wypisze False, bo zła kolejność
Console.WriteLine('\U00000010'); // wypisze znak o kodzie 16, czyli ►
Console.WriteLine('\u0010'); // jak wyżej
Console.WriteLine('\x10'); // jak wyżej
Console.WriteLine('►'); // jak wyżej
Console.WriteLine('\U0000FFFF'); // wypisze znak o kodzie 65535, czyli ? bo taki kod nie występuje w czcionce
Console.WriteLine('\U000C3BFA'); // błąd, bo znaki większe niż U+FFFF nie zmieszczą się w jednym char
Console.WriteLine("ala " + "ma" + " kota");
string fgh = "ala " + "ma " + (7 * 2 - 13) + " kota";

W liniach od 1 do 4 wyświetlamy długości (liczby znaków) łańcuchów. W liniach 5 i 6 wyświetlamy szesnastkowo (format X4) kody poszczególnych znaków z pary surogatów. W liniach 7 i 8, dzięki statycznym funkcjom logicznym typu Char o nazwach IsHighSurrogate i IsLowSurrogate, widzimy że pierwszy znak (znaki w łańcuchu numerowane są od zera, stąd [0]) jest high, a drugi low surrogate. W liniach 9 i 10 przedstawiamy działanie funkcji Char.IsSurrogatePair, która mówi nam, czy dwa znaki w danej kolejności tworzą poprawną parę surogatów. Linie 11, 12, 13 i 14 przedstawiają różne sposoby zapisu tego samego znaku. W linii 15 mamy największy znak Unicode, który zajmuje 16 bitów w UTF-16, a w linii 16 mamy błąd kiedy próbujemy zdefiniować stałą znakową ze znakiem Unicode o większym kodzie. Do tego potrzebujemy już 2 znaków, czyli łańcuch, jak w linii 17. W linii 18 widzimy, że łańcuchy możemy do siebie dodawać znakiem + i wypisany zostanie łańcuch, który jest wynikiem tego dodawania (sklejania). W linii 19 widzimy, że łańcuchy można sklejać z innymi obiektami, a wtedy obiekty te zostaną zamienione na ich reprezentację tekstową przez ich metody ToString() i całość zostanie sklejona w jeden łańcuch. Nawias jest potrzebny. Spróbuj go usunąć i okaże się że będzie błąd polegający na tym, że nie można odejmować liczby od łańcucha. Tylko dodawanie działa w ten sposób z łańcuchami.

Formatowanie

Metoda Console.WriteLine w liniach 5 i 6 w poprzednim przykładzie przyjmuje 3 argumenty: łańcuch znaków będący formatem i dwie liczby typu int. Ogólnie metoda ta jeśli ma jeden parametr, to po prostu wypisuje go na konsoli jeśli jest on łańcuchem. Jeśli parametr ten nie jest łańcuchem, to wypisuje łańcuch, który jest wynikiem działania funkcji parametr.ToString(). Wszystko ma metodę ToString(), bo dziedziczy ją po typie object. Można się spierać co jest filozoficznie bardziej podstawowe: object, czy string. Jeśli metoda Console.WriteLine ma więcej niż jeden parametr, to pierwszy jest formatem, a pozostałe są obiektami dowolnego typu. W formacie występują odwołania do pozostałych argumentów podanych w wywołaniu metody i tak {0} oznacza pierwszy argument po formacie, {1} – drugi, itd. Fragment łańcucha w nawiasach klamrowych jest całkowicie zastępowany przez odpowiednio sformatowaną reprezentację tekstową obiektu. Co to znaczy odpowiednio sformatowaną? Formatowanie jest po dwukropku, na przykład {0:X4} oznacza 4-cyfrowy zapis heksadecymalny (szesnastkowy). Szczegóły formatowania w artykule Jak wyświetlić tekst, liczbę.

Inne funkcje typu String

  • Clone – omówiona w rozdziale o tablicach
  • Equals, CompareTo – porównywanie ciągów znaków
  • ToLower, ToUpper – zamiana lter na małe albo na duże
  • EndsWith, StartsWith, Contains – czy string zawiera określony podciąg na końcu, na początku lub wewnątrz
  • IndexOfAny, IndexOf, LastIndexOfAny, LastIndexOf, ToCharArray – szukanie poszczególnych znaków
  • Insert, Remove, Replace – wstawianie, usuwanie i zamiana pdciągów
  • Substring – zwracanie podciągu
  • Trim, TrimStart, TrimEnd, PadLeft, PadRight – usuwanie powtarzających się znaków z początku, z końca, albo dopełnianie znakami z lewej lub z prawej do określonej długości
  • Split – podział stringu na tablicę stringów w miejscach wskazanych przez określone znaki lub podciągi

Spis treści Poprzednia strona: Wyliczenia Następna strona: Data i czas

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *