Do tego momentu podstawowe typy danych, które sprawdziliśmy, były używane do przechowywania liczb (całkowitych i zmiennoprzecinkowych) lub wartości prawda/fałsz (booleany). Ale co, jeśli chcemy przechowywać litery lub znaki interpunkcyjne?
#include <iostream>
int main()
{
std::cout << "Would you like a burrito? (y/n)";
// We want the user to enter a 'y' or 'n' character
// How do we do this?
return 0;
}Klasa char Typ danych został zaprojektowany tak, aby przechowywać pojedynczy character. A znak może to być pojedyncza litera, cyfra, symbol lub spacja.
Typ danych char jest typem całkowitym, co oznacza, że wartość bazowa jest przechowywana jako liczba całkowita. Podobnie jak wartość logiczna 0 jest interpretowana jako false a wartość różna od zera jest interpretowana jako true, liczby całkowite przechowywane przez char zmienną są interpretowane jako ASCII character.
ASCII skrót od American Standard Code for Information Interchange i definiuje szczególny sposób reprezentacji angielskich znaków (plus kilka innych symboli) jako liczby od 0 do 127 (zwane kodem ASCII lub punktem kodowym). Na przykład kod ASCII 97 jest interpretowany jako znak „a”.
Literały znakowe są zawsze umieszczane w pojedynczych cudzysłowach (np. „g”, „1”, „”).
Oto pełna tabela znaków ASCII znaki:
| Kod | Symbol | Kod | Symbol | Kod | Symbol | Kod | Symbol |
|---|---|---|---|---|---|---|---|
| 0 | NUL (null) | 32 | (spacja) | 64 | @ | 96 | ` |
| 1 | SOH (początek nagłówka) | 33 | ! | 65 | A | 97 | a |
| 2 | STX (początek tekstu) | 34 | ” | 66 | B | 98 | b |
| 3 | ETX (koniec tekstu) | 35 | # | 67 | C | 99 | c |
| 4 | EOT (koniec transmisji) | 36 | $ | 68 | D | 100 | d |
| 5 | ENQ (zapytanie) | 37 | % | 69 | E | 101 | e |
| 6 | ACK (potwierdzenie) | 38 | & | 70 | F | 102 | f |
| 7 | BEL (dzwonek) | 39 | ’ | 71 | G | 103 | g |
| 8 | BS (backspace) | 40 | ( | 72 | H | 104 | h |
| 9 | HT (zakładka pozioma) | 41 | ) | 73 | I | 105 | i |
| 10 | LF (przesunięcie wiersza/nowy linia) | 42 | * | 74 | J | 106 | j |
| 11 | VT (zakładka pionowa) | 43 | + | 75 | K | 107 | k |
| 12 | FF (wysuw formularza / nowa strona) | 44 | , | 76 | L | 108 | l |
| 13 | CR (karetka powrót) | 45 | - | 77 | M | 109 | m |
| 14 | SO (przesunięcie na zewnątrz) | 46 | . | 78 | N | 110 | n |
| 15 | SI (przesunięcie na zewnątrz) | 47 | / | 79 | O | 111 | o |
| 16 | DLE (łącze danych ucieczka) | 48 | 0 | 80 | P | 112 | p |
| 17 | DC1 (kontrola danych 1) | 49 | 1 | 81 | Q | 113 | q |
| 18 | DC2 (kontrola danych 2) | 50 | 2 | 82 | R | 114 | r |
| 19 | DC3 (kontrola danych 3) | 51 | 3 | 83 | S | 115 | S |
| 20 | DC4 (kontrola danych 4) | 52 | 4 | 84 | T | 116 | t |
| 21 | NAK (potwierdzenie negatywne) | 53 | 5 | 85 | U | 117 | u |
| 22 | SYN (synchroniczne idle) | 54 | 6 | 86 | V | 118 | v |
| 23 | ETB (koniec bloku transmisji) | 55 | 7 | 87 | W | 119 | w |
| 24 | CAN (anulowanie) | 56 | 8 | 88 | X | 120 | x |
| 25 | EM (koniec medium) | 57 | 9 | 89 | Y | 121 | y |
| 26 | SUB (zastępczy) | 58 | : | 90 | Z | 122 | z |
| 27 | ESC (escape) | 59 | ; | 91 | [ | 123 | { |
| 28 | FS (separator pliku) | 60 | < | 92 | \ | 124 | | |
| 29 | GS (separator grupy) | 61 | = | 93 | ] | 125 | } |
| 30 | RS (separator rekordu) | 62 | > | 94 | ^ | 126 | ~ |
| 31 | US (separator jednostek) | 63 | ? | 95 | _ | 127 | DEL (usuń) |
Kody Wartości 0-31 i 127 nazywane są znakami niedrukowalnymi. Kody te miały na celu sterowanie urządzeniami peryferyjnymi, takimi jak drukarki (np. poprzez instruowanie drukarki, jak ma poruszać głowicą drukującą). Większość z nich jest już nieaktualna. Jeśli spróbujesz wydrukować te znaki, wynik będzie zależny od Twojego systemu operacyjnego (może pojawić się kilka znaków podobnych do emoji).
Kody 32-126 to tak zwane znaki drukowalne i reprezentują one litery, cyfry i znaki interpunkcyjne używane przez większość komputerów do wyświetlania podstawowego tekstu w języku angielskim.
Jeśli spróbujesz wydrukować znak, którego wartość wykracza poza zakres ASCII, wyniki zależą również od Twojej OS.
Inicjowanie znaków
Możesz inicjować zmienne znakowe za pomocą literałów znakowych:
char ch2{ 'a' }; // initialize with code point for 'a' (stored as integer 97) (preferred)Możesz inicjować znaki również liczbami całkowitymi, ale jeśli to możliwe, należy tego unikać
char ch1{ 97 }; // initialize with integer 97 ('a') (not preferred)Ostrzeżenie
Uważaj, aby nie pomylić liczb znakowych z liczbami całkowitymi. Następujące dwie inicjalizacje nie są takie same:
char ch{5}; // initialize with integer 5 (stored as integer 5)
char ch{'5'}; // initialize with code point for '5' (stored as integer 53)Liczby znakowe mają być używane, gdy chcemy przedstawić liczby jako tekst, a nie jako liczby, na których można zastosować operacje matematyczne.
Drukowanie znaków
Gdy używasz std::cout do wydrukowania znaku, std::cout wyświetla zmienną char jako znak ASCII:
#include <iostream>
int main()
{
char ch1{ 'a' }; // (preferred)
std::cout << ch1; // cout prints character 'a'
char ch2{ 98 }; // code point for 'b' (not preferred)
std::cout << ch2; // cout prints a character ('b')
return 0;
}Daje to wynik:
ab
Możemy również bezpośrednio wyprowadź literały znaków:
std::cout << 'c';Daje to wynik:
c
Wprowadzanie znaków
Następujący program prosi użytkownika o wprowadzenie znaku, a następnie go wypisuje:
#include <iostream>
int main()
{
std::cout << "Input a keyboard character: ";
char ch{};
std::cin >> ch;
std::cout << "You entered: " << ch << '\n';
return 0;
}Oto wynik jednego uruchomienia:
Input a keyboard character: q You entered: q
Zauważ, że std::cin pozwoli Ci wprowadzić wiele znaków. Jednakże zmienna ch może pomieścić tylko 1 znak. W rezultacie do zmiennej ch wyodrębniany jest tylko pierwszy znak wejściowy. Pozostała część danych wejściowych użytkownika pozostaje w buforze wejściowym używanym przez std::cin i może zostać wyodrębniona przy kolejnych wywołaniach std::cin.
Możesz zobaczyć to zachowanie w następującym przykładzie:
#include <iostream>
int main()
{
std::cout << "Input a keyboard character: "; // assume the user enters "abcd" (without quotes)
char ch{};
std::cin >> ch; // ch = 'a', "bcd" is left queued.
std::cout << "You entered: " << ch << '\n';
// Note: The following cin doesn't ask the user for input, it grabs queued input!
std::cin >> ch; // ch = 'b', "cd" is left queued.
std::cout << "You entered: " << ch << '\n';
return 0;
}Input a keyboard character: abcd You entered: a You entered: b
Jeśli chcesz wczytać więcej niż jeden znak na raz (np. wczytać imię, słowo lub zdanie), zamiast znaku użyj ciągu znaków. Łańcuch to zbiór kolejnych znaków (a zatem ciąg może zawierać wiele symboli). Omówimy to na nadchodzącej lekcji (5.7 — Wprowadzenie do std::string).
Wyodrębnianie białych znaków
Ponieważ wyodrębnianie danych wejściowych ignoruje początkowe białe znaki, może to prowadzić do nieoczekiwanych wyników podczas próby wyodrębnienia białych znaków do zmiennej char:
#include <iostream>
int main()
{
std::cout << "Input a keyboard character: "; // assume the user enters "a b" (without quotes)
char ch{};
std::cin >> ch; // extracts a, leaves " b\n" in stream
std::cout << "You entered: " << ch << '\n';
std::cin >> ch; // skips leading whitespace (the space), extracts b, leaves "\n" in stream
std::cout << "You entered: " << ch << '\n';
return 0;
}Input a keyboard character: a b You entered: a You entered: b
W powyższym przykładzie mogliśmy spodziewać się wyodrębnienia spacji, ale ponieważ początkowe białe znaki zostały pominięte, wyodrębniliśmy znak b zamiast tego.
Jednym z prostych sposobów rozwiązania tego problemu jest użycie funkcji std::cin.get() do przeprowadzenia ekstrakcji, ponieważ ta funkcja nie ignoruje wiodących białych znaków:
#include <iostream>
int main()
{
std::cout << "Input a keyboard character: "; // assume the user enters "a b" (without quotes)
char ch{};
std::cin.get(ch); // extracts a, leaves " b\n" in stream
std::cout << "You entered: " << ch << '\n';
std::cin.get(ch); // extracts space, leaves "b\n" in stream
std::cout << "You entered: " << ch << '\n';
return 0;
}Input a keyboard character: a b You entered: a You entered:
Rozmiar, zakres i domyślny znak
Znak jest zdefiniowany w C++ tak, aby zawsze miał rozmiar 1 bajtu przechowuj znaki ASCII, nie musisz określać znaku (ponieważ zarówno znaki ze znakiem, jak i znaki bez znaku mogą przechowywać wartości od 0 do 127).
Jeśli używasz znaku do przechowywania małych liczb całkowitych (czego nie powinieneś robić, chyba że jawnie optymalizujesz spację), powinieneś zawsze określić, czy jest on ze znakiem, czy bez znaku. Znak ze znakiem może zawierać liczbę od -128 do 127. Znak bez znaku może przechowuj liczbę od 0 do 255.
Sekwencje ucieczki
W C++ istnieją pewne sekwencje znaków, które mają specjalne znaczenie. Znaki te nazywane są sekwencjami ucieczki Sekwencja ucieczki zaczyna się od znaku „\” (ukośnik odwrotny), a następnie następuje litera lub cyfra.
Poznałeś już najczęstszą ucieczkę. sekwencja: '\n', której można użyć do wydrukowania nowej linii:
#include <iostream>
int main()
{
int x { 5 };
std::cout << "The value of x is: " << x << '\n'; // standalone \n goes in single quotes
std::cout << "First line\nSecond line\n"; // \n can be embedded in double quotes
return 0;
}To daje:
The value of x is: 5 First line Second line
Inną powszechnie używaną sekwencją ucieczki jest '\t', która zawiera poziomą tabulator:
#include <iostream>
int main()
{
std::cout << "First part\tSecond part";
return 0;
}Co daje:
First part Second part
Trzy inne godne uwagi sekwencje ucieczki to:
\’ drukuje pojedynczy cudzysłów
\” drukuje podwójny cudzysłów
\\ drukuje ukośnik odwrotny
Oto tabela wszystkich sekwencji ucieczki:
| Nazwa | Symbol | Znaczenie |
|---|---|---|
| Alert | \a | Wykonuje alert, np. sygnał dźwiękowy |
| Backspace | \b | Przesuwa kursor o jedną spację |
| Formfeed | \f | Przesuwa kursor do następnej strony logicznej |
| Nowa linia | \n | Przesuwa kursor do następnej linia |
| Powrót karetki | \r | Przesuwa kursor na początek linii |
| Zakładka pozioma | \t | Drukuje zakładkę poziomą |
| Pionowo tab | \v | Drukuje pionowy cudzysłów |
| Pojedynczy cudzysłów | \’ | Drukuje pojedynczy cudzysłów |
| Podwójny cudzysłów | \” | Drukuje podwójny cudzysłów |
| Ukośnik odwrotny | \\ | Drukuje ukośnik odwrotny. |
| Znak zapytania | \? | Drukuje znak zapytania. Nie ma już znaczenia. Możesz używać znaków zapytania bez ucieczki. |
| Liczba ósemkowa | \(liczba) | Przelicza na znak reprezentowany przez ósemkę |
| Hex liczba | \x(liczba) | Tłumaczy na znak reprezentowany przez liczbę szesnastkową |
Oto kilka przykładów:
#include <iostream>
int main()
{
std::cout << "\"This is quoted text\"\n";
std::cout << "This string contains a single backslash \\\n";
std::cout << "6F in hex is char '\x6F'\n";
return 0;
}Drukuje:
"This is quoted text" This string contains a single backslash \ 6F in hex is char 'o'
Ostrzeżenie
Sekwencje ucieczki rozpoczynają się od ukośnika odwrotnego (\), a nie od ukośnika (/). Jeśli przez przypadek użyjesz ukośnika, może się on jeszcze skompilować, ale nie przyniesie oczekiwanego rezultatu.
Nowa linia (\n) vs. std::endl
Omówimy ten temat w lekcji 1.5 — Wprowadzenie do iostream: cout, cin i endl.
Jaka jest różnica pomiędzy umieszczaniem symboli w pojedynczych i podwójnych cudzysłowy?
Tekst zawarty w pojedynczych cudzysłowach jest traktowany jako char literał reprezentujący pojedynczy znak. Na przykład 'a' reprezentuje znak a, '+' reprezentuje znak plusa, '5' reprezentuje znak 5 (a nie cyfrę 5) i '\n' reprezentuje znak nowej linii.
Tekst zawarty w cudzysłowie (np. „Hello, world!”) jest traktowany jako ciąg znaków w stylu C literał, który może zawierać wiele znaków. Ciągi omawiamy na lekcji 5.2 -- Literały.
Najlepsza praktyka
Pojedyncze znaki powinny być zwykle ujęte w pojedynczy cudzysłów, a nie w podwójny cudzysłów (np. 't' lub '\n', nie "t" lub "\n"). Jeden z możliwych wyjątków ma miejsce podczas wykonywania wyników, gdzie preferowane może być podwójne cudzysłowy dla zachowania spójności (zobacz lekcję 1.5 — Wprowadzenie do iostream: cout, cin i endl).
Unikaj literałów wieloznakowych
Ze względu na kompatybilność wsteczną wiele kompilatorów C++ obsługuje literały wieloznakowe, co to literały char zawierające wiele znaków (np. '56'). Jeśli są obsługiwane, mają one wartość zdefiniowaną w implementacji (co oznacza, że różni się ona w zależności od kompilatora). Ponieważ nie są częścią standardu C++ i ich wartość nie jest ściśle zdefiniowana, należy unikać literałów wieloznakowych.
Najlepsza praktyka
Unikaj literałów wieloznakowych (np. '56').
Obsługa literałów wieloznakowych często powoduje problemy dla nowych programistów, gdy zapominają, czy sekwencje specjalne używają ukośników, czy ukośników odwrotnych:
#include <iostream>
int add(int x, int y)
{
return x + y;
}
int main()
{
std::cout << add(1, 2) << '/n'; // we used a forward slash instead of a backslash here
return 0;
}Programista oczekuje, że program wydrukuje wartość 3 i znak nowej linii, ale zamiast tego na maszynie autora wyświetla następujący komunikat:
312142
Problem polega na tym, że programista przypadkowo użył '/n' (literału wieloznakowego składającego się z ukośnika i ukośnika). 'n' znak) zamiast '\n' (sekwencja ucieczki dla nowej linii). Program najpierw wypisuje 3 (wynik add(1, 2)) poprawnie, ale następnie wypisuje wartość literału wieloznakowego '/n', który na maszynie autora miał zdefiniowaną w implementacji wartość 12142.
Ostrzeżenie
Upewnij się, że nowe linie są używając sekwencji ucieczki '\n' , a nie literału wieloznakowego '/n'.
Kluczowa informacja
Zauważ, że gdybyśmy umieścili wynik w cudzysłowie "/n", program wydrukowałby 3/n, co nadal jest błędne, ale znacznie mniej zagmatwane.
Oto kolejny przykład. Zacznijmy od następującego:
#include <iostream>
int main()
{
int x { 5 };
std::cout << "The value of x is " << x << '\n';
return 0;
}Ten program wyświetla dokładnie to samo, co ty spodziewaj się:
The value of x is 5
Ale ten wynik nie jest wystarczająco ekscytujący, więc decydujemy się dodać wykrzyknik przed nową linią:
#include <iostream>
int main()
{
int x { 5 };
std::cout << "The value of x is " << x << '!\n'; // added exclamation point
return 0;
}Chociaż spodziewamy się, że spowoduje to następujący wynik:
The value of x is 5!
Ponieważ '!\n' jest literałem wieloznakowym, na maszynie autora, to faktycznie zostało wydrukowane:
The value of x is 58458
To jest nie tylko niepoprawne, ale może być trudne do debugowania, ponieważ prawdopodobnie zakładasz który x ma niewłaściwą wartość.
Używanie podwójnych cudzysłowów podczas wyprowadzania literałów znakowych (zamiast pojedynczych cudzysłowów) albo ułatwia wykrywanie tego rodzaju problemów, albo pozwala ich całkowicie uniknąć.
A co z innymi typami znaków, wchar_t, char8_t, char16_t, I char32_t?
Podobnie jak ASCII odwzorowuje liczby całkowite 0-127 na znaki w amerykańskim języku angielskim, istnieją inne standardy kodowania znaków, które odwzorowują liczby całkowite (o różnych rozmiarach) na znaki w innych językach. Najbardziej znanym mapowaniem poza ASCII jest standard Unicode, który odwzorowuje ponad 144 000 liczb całkowitych na znaki w wielu różnych językach. Ponieważ Unicode zawiera tak wiele punktów kodowych, pojedynczy punkt kodowy Unicode potrzebuje 32 bitów do reprezentowania znaku (tzw. UTF-32). Jednak znaki Unicode można również kodować przy użyciu wielu znaków 16-bitowych lub 8-bitowych (zwanych odpowiednio UTF-16 i UTF-8).
char16_t i char32_t zostały dodane do C++ 11, aby zapewnić wyraźną obsługę 16-bitowych i 32-bitowych znaków Unicode. Te typy znaków mają ten sam rozmiar, co odpowiednio std::uint_least16_t i std::uint_least32_t (ale są to różne typy). char8_t został dodany w C++ 20, aby zapewnić obsługę 8-bitowego Unicode (UTF-8). Jest to odrębny typ, który używa tej samej reprezentacji co unsigned char.
Nie będziesz musiał używać char8_t, char16_t lub char32_t chyba że planujesz uczynić swój program zgodnym z Unicode. wchar_t należy unikać w prawie wszystkich przypadkach (z wyjątkiem interfejsu API systemu Windows), ponieważ jego rozmiar jest zdefiniowany w implementacji.
Unicode i lokalizacja generalnie wykraczają poza zakres tych samouczków, więc nie będziemy ich omawiać dalej. W międzyczasie podczas pracy ze znakami (i ciągami znaków) należy używać wyłącznie znaków ASCII. Używanie znaków z innych zestawów znaków może spowodować nieprawidłowe wyświetlanie znaków.

