Znaki, char i Unicode
Robot odczytuje kod z identyfikatora: A7B2. Zanim policzysz w nim cyfry, musisz umieć wyjąć z tekstu pojedynczy znak i odróżnić znak cyfry od jej wartości.
Czego się dziś nauczysz
- Odróżnisz literał
charod jednoznakowego napisu - Wyjmiesz z napisu znak po indeksie i podasz indeks ostatniego znaku
- Wyjaśnisz, dlaczego
'7'to nie liczba 7, i zamienisz znak cyfry na jej wartość - Użyjesz
char.IsDigit,IsLetteri pokrewnych - Wyjaśnisz, dlaczego emoji ma
Lengthrówne 2
Przygotowanie: lekcje 01–06. Przewidywany czas: 45–90 minut z zadaniami. Przykłady wymagają .NET 8 lub nowszego, z włączonymi ImplicitUsings i Nullable.
Jeden znak to nie to samo co tekst
W lekcji 06 poznałeś typ string — tekst dowolnej długości. Obok niego istnieje typ char, który mieści dokładnie jeden znak. Różni je nie tylko rozmiar, ale i zapis:
char litera = 'A'; // APOSTROFY - jeden znak
string napis = "A"; // CUDZYSLOW - tekst o dlugosci 1
char pusty = ' '; // spacja to tez znak
// char zly = "A"; // BLAD CS0029: nie mozna przypisac string do char
// char zly2 = 'Ala'; // BLAD CS1012: w apostrofach mieszcza sie tylko znak
Napis udostępnia swoje znaki po numerze — indeksie. Numerujemy od zera, tak samo jak później elementy tablic.
string kod = "A7B2";
Console.WriteLine(kod.Length); // 4 - liczba znakow
Console.WriteLine(kod[0]); // A - pierwszy znak
Console.WriteLine(kod[1]); // 7
Console.WriteLine(kod[3]); // 2 - ostatni, bo Length - 1
4
A
7
2
Ostatni indeks to Length - 1
Napis o czterech znakach ma indeksy 0, 1, 2 i 3. Odwołanie kod[4] kończy się wyjątkiem IndexOutOfRangeException i przerwaniem programu. Ta sama reguła obowiązuje przy tablicach w lekcji 23 warto ją zapamiętać raz.
Znak można wyjąć z napisu i przypisać do zmiennej typu char, a pojedynczy znak można doliczyć do napisu:
string kod = "A7B2";
char pierwszy = kod[0];
Console.WriteLine(pierwszy); // A
Console.WriteLine("Kod zaczyna sie od " + pierwszy);
Console.WriteLine($"Ostatni znak: {kod[kod.Length - 1]}");
Znak to liczba w przebraniu
Komputer nie przechowuje kształtu litery — przechowuje jej numer. Rzutowanie na int pokazuje ten numer:
Console.WriteLine((int)'A'); // 65
Console.WriteLine((int)'a'); // 97
Console.WriteLine((int)'0'); // 48
Console.WriteLine((int)'7'); // 55
Console.WriteLine((char)66); // B - w druga strone tez dziala
Stąd bierze się pułapka, która myli niemal każdego na początku: znak cyfry to nie jest wartość cyfry. Znak '7' ma numer 55, a nie 7.
char cyfra = '7';
Console.WriteLine((int)cyfra); // 55 - numer znaku
Console.WriteLine(cyfra - '0'); // 7 - WARTOSC cyfry
Console.WriteLine('9' - '0'); // 9
Console.WriteLine('A' - '0'); // 17 - bez sensu, bo 'A' nie jest cyfra
Dlaczego odejmowanie działa
Cyfry ASCII leżą w tablicy znaków po kolei: '0' ma numer 48, '1' — 49, aż do '9' o numerze 57. Odjęcie numeru zera od numeru dowolnej cyfry daje jej wartość. Ten sam trik działa dla liter: 'C' - 'A' daje 2, czyli numer litery w alfabecie łacińskim.
Ponieważ znaki mają numery, można je porównywać operatorami znanymi z arytmetyki:
Console.WriteLine('a' > 'A'); // True - 97 > 65
Console.WriteLine('b' > 'a'); // True - kolejnosc alfabetyczna
Console.WriteLine('Z' < 'a'); // True - WSZYSTKIE wielkie litery przed malymi
Kolejność nie jest alfabetem polskim
Porównywanie numerów ustawia znaki w kolejności tablicy Unicode, nie słownikowej. 'ż' > 'z' da true, bo polskie litery mają wyższe numery, a wielkie litery wypadają przed wszystkimi małymi. Do porządkowania nazwisk służą osobne narzędzia — poznasz je przy porównywaniu napisów w lekcji 08 i przy sortowaniu w lekcji 51.
Rozpoznawanie: char.IsDigit i spółka
Zamiast porównywać numery ręcznie, korzystamy z gotowych metod klasy char. Każda zwraca bool — wartość logiczną z lekcji 06.
char z = '7';
Console.WriteLine(char.IsDigit(z)); // True
Console.WriteLine(char.IsLetter(z)); // False
Console.WriteLine(char.IsLetterOrDigit(z));// True
Console.WriteLine(char.IsWhiteSpace(z)); // False
Console.WriteLine(char.IsUpper('a')); // False
Console.WriteLine(char.IsLower('a')); // True
Console.WriteLine(char.ToUpper('ł')); // Ł
Console.WriteLine(char.ToLower('X')); // x
Wynik bool możesz na razie po prostu wypisać. Od lekcji 14 użyjesz go w instrukcji warunkowej, a od lekcji 17 w pętli przechodzącej po wszystkich znakach napisu. Wtedy z tych metod zbudujesz sprawdzanie haseł, numerów PESEL i kodów.
IsDigit nie zawsze znaczy „0–9″
char.IsDigit uznaje za cyfrę każdy znak, który w Unicode jest cyfrą dziesiętną — także cyfry arabsko-indyjskie czy dewanagari. Dla nich odejmowanie - '0' da bezsensowny wynik. W zadaniach szkolnych to nie przeszkadza, ale przy danych z internetu warto o tym pamiętać.
Dlaczego emoji ma Length równe 2
char zajmuje 16 bitów, czyli dwa bajty. To wystarcza na ponad 65 tysięcy znaków — wszystkie litery alfabetów europejskich, cyfry i znaki interpunkcyjne. Nie wystarcza jednak na cały Unicode, który ma ich ponad milion.
Znaki spoza tego zakresu — emoji, rzadkie znaki chińskie, symbole matematyczne — zapisywane są parą jednostek char. Napis liczy jednostki, nie symbole widziane przez człowieka:
Console.WriteLine("A".Length); // 1
Console.WriteLine("ł".Length); // 1 - polskie litery mieszcza sie w jednym char
Console.WriteLine("€".Length); // 1
Console.WriteLine("😀".Length); // 2 - jeden symbol, DWIE jednostki
| Wyrażenie | Wynik | Dlaczego |
|---|---|---|
"Ala".Length | 3 | trzy zwykłe znaki |
"żółw".Length | 4 | polskie litery mieszczą się w jednym char |
"😀".Length | 2 | symbol spoza podstawowego zakresu — para jednostek |
"a\tb".Length | 3 | tabulator to jeden znak, mimo że wygląda jak kilka spacji |
Znaki specjalne
Niektórych znaków nie da się wpisać wprost — zapisujemy je sekwencją zaczynającą się od ukośnika wstecznego:
| Zapis | Znaczenie |
|---|---|
\n | przejście do nowego wiersza |
\t | tabulator |
\\ | ukośnik wsteczny |
\" | cudzysłów wewnątrz napisu |
\' | apostrof wewnątrz char |
\u0141 | znak o podanym numerze szesnastkowym (tu: Ł) |
Console.WriteLine("Pierwszy\nDrugi");
Console.WriteLine("Imie:\tAla");
Console.WriteLine("Sciezka: C:\\dane\\plik.txt");
Console.WriteLine("Powiedzial \"dobrze\" i wyszedl.");
Console.WriteLine('\u0141');
Pierwszy
Drugi
Imie: Ala
Sciezka: C:\dane\plik.txt
Powiedzial "dobrze" i wyszedl.
Ł
Przykład: rozbiór kodu identyfikatora
Wklej całość do nowego projektu konsolowego jako Program.cs. Zanim uruchomisz — przewidź każdy wynik.
string kod = "A7B2";
Console.WriteLine($"Kod: {kod}, dlugosc: {kod.Length}");
Console.WriteLine();
char pierwszy = kod[0];
char drugi = kod[1];
Console.WriteLine($"[0] = {pierwszy} litera? {char.IsLetter(pierwszy)} cyfra? {char.IsDigit(pierwszy)}");
Console.WriteLine($"[1] = {drugi} litera? {char.IsLetter(drugi)} cyfra? {char.IsDigit(drugi)}");
Console.WriteLine();
Console.WriteLine($"numer znaku '{drugi}' to {(int)drugi}");
Console.WriteLine($"wartosc cyfry '{drugi}' to {drugi - '0'}");
Console.WriteLine();
int suma = (kod[1] - '0') + (kod[3] - '0');
Console.WriteLine($"Suma cyfr na pozycjach 1 i 3: {suma}");
Console.WriteLine($"Kod malymi literami: {kod.ToLower()}");
Kod: A7B2, dlugosc: 4
[0] = A litera? True cyfra? False
[1] = 7 litera? False cyfra? True
numer znaku '7' to 55
wartosc cyfry '7' to 7
Suma cyfr na pozycjach 1 i 3: 9
Kod malymi literami: a7b2
Co dzieje się po kolei
kod.Lengthto właściwość — odczytujemy ją bez nawiasów.char.IsLetter(x)to metoda — wymaga nawiasów i argumentu.- Wewnątrz interpolacji
$"…"wolno umieścić całe wyrażenie:{(int)drugi}albo{drugi - '0'}. - Nawiasy w
(kod[1] - '0')nie są konieczne, ale pokazują zamiar: najpierw wyjmij znak, potem zamień na wartość. ToLowerzwraca nowy napis. Zmiennakodnadal zawieraA7B2— dlaczego, wyjaśnia następna lekcja.
Co robi każdy użyty element
| Element | Wynik | Działanie i ograniczenia |
|---|---|---|
'x' | char | Literał znakowy — apostrofy, dokładnie jeden znak. "x" to napis, nie znak. |
napis[i] | char | Znak o indeksie i, licząc od zera. Indeks od Length w górę daje IndexOutOfRangeException. |
napis.Length | int | Liczba jednostek char — właściwość, bez nawiasów. Nie zawsze równa liczbie widocznych symboli. |
(int)znak | int | Numer jednostki kodowej UTF-16. Rzutowanie odwrotne (char)liczba też działa. |
znak - '0' | int | Wartość cyfry — poprawna tylko dla cyfr ASCII od '0' do '9'. |
char.IsDigit(z) | bool | Czy znak jest cyfrą dziesiętną Unicode. Argumentu nie zmienia. |
char.IsLetter(z) | bool | Czy znak jest literą. Jest też IsLetterOrDigit. |
char.IsWhiteSpace(z) | bool | Spacja, tabulator, znak końca wiersza. |
char.IsUpper(z) / IsLower(z) | bool | Wielkość litery. Dla cyfr i znaków interpunkcyjnych oba dają false. |
char.ToUpper(z) / ToLower(z) | char | Zwraca zmieniony znak; oryginału nie rusza. Wersje …Invariant ignorują ustawienia regionalne. |
\n, \t, \\, \" | znak | Sekwencje ucieczki — jeden znak mimo dwóch symboli w kodzie. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
char z = "A"; | CS0029 — cudzysłów tworzy string. Znak wymaga apostrofów. |
int x = '7'; | Kompiluje się, ale x będzie miało 55, a nie 7. Użyj '7' - '0'. |
kod[kod.Length] | Zawsze poza zakresem. Ostatni znak to kod[kod.Length - 1]. |
kod[0] na pustym napisie | Wyjątek. Pusty napis "" ma Length równe 0 i żadnego indeksu. |
char.ToUpper(z) bez przypisania | Metoda zwraca nowy znak, starego nie zmienia. Zapisz wynik: z = char.ToUpper(z);. |
Zakładanie, że Length to liczba symboli | Emoji i część znaków azjatyckich zajmują dwie jednostki. |
Zadania
Wypisz numery znaków 'A', 'Z', 'a', '0' i ' '. Następnie wypisz, jakie znaki kryją się pod numerami 65, 90 i 122.
Dla napisu "Sala 204" wypisz jego długość oraz znaki o indeksach 0, 4 i ostatnim. Ostatni pobierz wyrażeniem z Length, a nie wpisaną na sztywno liczbą.
Dla znaków 'K', '4', ' ' i 'ż' wypisz wynik char.IsLetter, char.IsDigit i char.IsWhiteSpace. Zestaw wyniki w kolumnach oddzielonych tabulatorem.
Z napisu "9081" wyjmij pierwszą i ostatnią cyfrę, zamień je na wartości liczbowe i wypisz ich sumę oraz iloczyn. Wypisz też, ile wyszłoby, gdyby zapomnieć o odjęciu '0'.
Wypisz, którą literą alfabetu łacińskiego jest 'C', 'M' i 'Z' — licząc od 1. Skorzystaj z odejmowania znaków. Sprawdź, co wypisze twój kod dla 'c', i wyjaśnij wynik.
Wypisz Length napisów "A", "ł", "€", "😀" i "a\tb". Przy każdym dopisz własnymi słowami, dlaczego wynik jest właśnie taki. Jeden z nich zaskoczy cię dwa razy.
Co trzeba zapamiętać
charmieści jeden znak i zapisujemy go w apostrofach;string— w cudzysłowie.- Znaki napisu numerujemy od zera; ostatni ma indeks
Length - 1. - Znak jest przechowywany jako numer —
(int)'A'daje 65. - Znak cyfry to nie jej wartość:
'7' - '0'daje 7, samo'7'daje 55. char.IsDigit,IsLetter,IsWhiteSpacezwracająbooli niczego nie zmieniają.Lengthliczy jednostki UTF-16, a nie symbole widziane przez człowieka — emoji ma długość 2.
Dokumentacja: Microsoft Learn — temat tej lekcji.