Instrukcja przypisania i typ znakowy char
Część programistyczna: Instrukcja przypisania
W tej lekcji opowiemy o podstawowej metodzie nadawania wartości zmiennym – instrukcji przypisania. W najprostszej wersji już ją widzieliśmy: aby nadać zmiennej \(a\) typu int
wartość (na przykład) 5, możemy użyć następującej instrukcji:
a = 5;
Przypisania tego typu stosuje się w szczególności do ustawienia początkowej wartości zmiennej. Jeśli przypisanie następuje bezpośrednio po deklaracji zmiennej, możemy je połączyć z deklaracją:
int a = 5;
Zamiast pojedynczej liczby w przypisaniu może także wystąpić dowolne wyrażenie matematyczne. Na przykład:
int a, x, y;
cin >> x >> y;
a = 2 * x * y + 1;
Zmiennym możemy (oczywiście) wielokrotnie przypisywać różne wartości. To uzasadnia, dlaczego nazywamy je właśnie zmiennymi. Co ciekawe, zmienna, której przypisujemy nową wartość, może wystąpić także w wyrażeniu po prawej stronie instrukcji przypisania! Oto (bardzo typowa) przykładowa instrukcja:
a = a + 3;
Jesteśmy przyzwyczajeni do tego, że znak =
oznacza równość dwóch wartości, więc na pierwszy rzut oka taki zapis wydaje się nie mieć sensu – żadna liczba nie może być równa sobie samej zwiększonej o \(3\)! Ale w C++, jak również w wielu innych językach programowania znak =
nie jest równością, a poleceniem wstawienia wartości do odpowiedniej komórki. Instrukcja a = a + 3
jest interpretowana następująco: weź aktualną wartość zmiennej a
, dodaj do niej 3, a potem otrzymany wynik wpisz z powrotem do komórki oznaczonej a
. Innymi słowy, będzie to zwiększenie wartości zmiennej \(a\) o 3.
Załóżmy na przykład, że chcemy wczytać pewną liczbę, dodać do niej 3, pomnożyć przez 2, a następnie dodać jeszcze 1, po każdej operacji wypisując aktualną wartość. Prosty program realizujący te zadania wyglądać może tak:
int a;
cin >> a;
a = a + 3;
cout << a;
a = a * 2;
cout << a;
a = a + 1;
cout << a;
Wśród początkujących programistów zdarza się dość często taka konstrukcja:
int a, b, c, d;
cin >> a;
b = a + 3;
cout << b;
c = b * 2;
cout << c;
d = c + 1;
cout << d;
Na ogół jest to spowodowane "nieufnością" do instrukcji postaci a = a+3
. Postaraj się jak najszybciej pozbyć wątpliwości – wkrótce przekonasz się, że deklarowanie wielu dodatkowych, niepotrzebnych zmiennych na dłuższą metę przeszkadza znacznie bardziej.
Skrócone instrukcje przypisania
W języku C++ są też dostępne skrócone instrukcje przypisania. Otóż zamiast:
a = a + b;
a = a - b;
a = a * b;
a = a / b;
a = a % b;
możemy napisać odpowiednio:
a += b;
a -= b;
a *= b;
a /= b;
a %= b;
Zatem ostatni przykład można napisać jeszcze krócej:
int a;
cin >> a;
a += 3;
cout << a;
a *= 2;
cout << a;
a += 1;
cout << a;
Jak okaże się w kolejnych lekcjach, najczęściej do wartości zmiennej dodaje się 1, lub też odejmuje 1. Dlatego dla tych przypadków są jeszcze bardziej skrócone konstrukcje, zwane inkrementacją i dekrementacją:
a++; // to samo co: a += 1;
a--; // to samo co: a -= 1;
Do pierwszej z tych instrukcji nawiązuje sama nazwa języka C++, który został zaprojektowany jako rozszerzenie starszego języka programowania C. Co ciekawe, poprzednikiem języka C był język B.
A oto inny przykład. Zobaczymy, jak zastosowanie instrukcji przypisania może uprościć rozwiązanie zadania Czas z lekcji "Błędy w programach, pierwsze zadania". Przypomnijmy, że należało w nim przeliczyć czas \(t\) sekund na zapis w godzinach, minutach i sekundach.
To zadanie można oczywiście rozwiązać bez użycia instrukcji przypisania, jednak z jej pomocą rozwiązanie staje się bardziej przejrzyste. Klucz do rozwiązania stanowi wprowadzenie trzech zmiennych: \(g\), \(m\) i \(s\), które oznaczają odpowiednie fragmenty wyniku: liczbę pełnych godzin w czasie \(t\), liczbę pełnych minut w tym czasie z wyłączeniem pełnych godzin i pozostałą liczbę sekund. Wartości tych zmiennych możemy obliczać w kolejności \(g\), \(m\), \(s\) lub odwrotnie. W poniższym programie obraliśmy właśnie kolejność odwrotną, natomiast rozwiązanie wybierające kolejność zgodną z podaną pozostawiamy już uczestnikom kursu.
#include <iostream>
using namespace std;
int main() {
int t;
cin >> t;
int g, m, s;
s = t % 60;
t /= 60;
m = t % 60;
t /= 60;
g = t;
cout << g << "g" << m << "m" << s << "s" << endl;
}
Część techniczna: Typ znakowy char
W części technicznej lekcji wprowadzimy nowy typ zmiennych – typ znakowy char
. Pozwala on przechowywać pojedyncze znaki (małe i wielkie litery, cyfry, znaki przestankowe itp.). Wartości typu char
są w języku C++ otoczone apostrofami, np. 'a'
, '8'
, '+'
, '.'
.
Jako pierwszy przykład napiszmy program, który wczytuje dany znak, o którym wiemy, że jest małą literą, i sprawdza, czy jest to samogłoska, czy spółgłoska.
#include <iostream>
using namespace std;
int main() {
char c;
cin >> c;
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || c == 'y')
cout << "samogloska" << endl;
else
cout << "spolgloska" << endl;
}
Każdy ze znaków typu char
ma przypisany numer będący liczbą całkowitą. To przyporządkowanie, używane powszechnie w komputerach do reprezentowania znaków, nazywa się kodem ASCII. Wygląda ono tak:
kod | znak | kod | znak | kod | znak | kod | znak | kod | znak |
---|---|---|---|---|---|---|---|---|---|
0-31 | znaki specjalne | 51 | 3 | 71 | G | 91 | [ | 111 | o |
32 | spacja | 52 | 4 | 72 | H | 92 | \ | 112 | p |
33 | ! | 53 | 5 | 73 | I | 93 | ] | 113 | q |
34 | " | 54 | 6 | 74 | J | 94 | ^ | 114 | r |
35 | # | 55 | 7 | 75 | K | 95 | _ | 115 | s |
36 | $ | 56 | 8 | 76 | L | 96 | ` | 116 | t |
37 | % | 57 | 9 | 77 | M | 97 | a | 117 | u |
38 | & | 58 | : | 78 | N | 98 | b | 118 | v |
39 | ' | 59 | ; | 79 | O | 99 | c | 119 | w |
40 | ( | 60 | < | 80 | P | 100 | d | 120 | x |
41 | ) | 61 | = | 81 | Q | 101 | e | 121 | y |
42 | * | 62 | > | 82 | R | 102 | f | 122 | z |
43 | + | 63 | ? | 83 | S | 103 | g | 123 | { |
44 | , | 64 | @ | 84 | T | 104 | h | 124 | |
45 | - | 65 | A | 85 | U | 105 | i | 125 | } |
46 | . | 66 | B | 86 | V | 106 | j | 126 | ~ |
47 | / | 67 | C | 87 | W | 107 | k | 127 | znak specjalny |
48 | 0 | 68 | D | 88 | X | 108 | l | ||
49 | 1 | 69 | E | 89 | Y | 109 | m | ||
50 | 2 | 70 | F | 90 | Z | 110 | n |
Oczywiście nie trzeba pamiętać kodów ASCII poszczególnych znaków. Warto jedynie wiedzieć, że małe litery oraz wielkie litery alfabetu angielskiego (łacińskiego) są ustawione w kodzie kolejno w porządku alfabetycznym, a cyfry – od najmniejszej do największej. Porównywanie znaków typu char
za pomocą operatorów <
, <=
, >
, >=
odbywa się według kodów ASCII, tak więc małe litery oraz wielkie litery są porównywane alfabetycznie, a cyfry od najmniejszej do największej. Znaki o kodach od 0 do 31 oraz znak o kodzie 127 to tzw. kody sterujące. Znajdują się wśród nich m.in. znaki końca wiersza i tabulacji; wiele z tych znaków wyszło już z użycia.
Wartości zmiennych typu char
możemy więc traktować jako niewielkie liczby całkowite. Dokładniej, zmienna typu char
przyjmuje wartości od -128 do 127, przy czym wartości nieujemne odpowiadają znakom kodu ASCII, a pozostałe mogą służyć do reprezentowania innych symboli (np. polskich znaków ą, ę, ź, ć itp. w niektórych kodowaniach). Typ char
jest więc typem całkowitym jednobajtowym, którego brakowało w komentarzu do lekcji 2. Odpowiadającym mu typem całkowitym nieujemnym (o wartościach od 0 do 255) jest typ unsigned char
.
Przyjrzyjmy się, jakie konsekwencje ma ta dwoista natura typu char
.
Przypisując wartość zmiennej typu char
, możemy to zrobić, albo wstawiając żądany znak w apostrofy, albo podając numer tego znaku w kodzie ASCII. Czyli np. oba poniższe przypisania są równoważne:
char znak = 'a';
char znak = 97;
Z przyczyn technicznych przy wczytywaniu i wypisywaniu to już tak łatwo nie zadziała. Chodzi o to, że typ char
wczytuje i wypisuje znak, a nie liczbę. Jeśli więc przy takim fragmencie programu:
char znak;
cin >> znak;
wpiszemy na wejściu liczbę 97, to zamiast znaku 'a' o kodzie ASCII 97 zostanie wczytany po prostu znak '9', jako pierwszy znak na wejściu!
Aby wczytać znak o danym kodzie ASCII, należy wczytać ten kod jako liczbę całkowitą innego typu niż char
(np. typu int
) i przypisać zmiennej znak wczytaną wartość:
int kod;
char znak;
cin >> kod;
znak = kod;
Oczywiście zadziała to tylko wtedy, gdy kod będzie faktycznie liczbą z zakresu typu char
– w przeciwnym razie wystąpi znany nam już błąd przekroczenia zakresu typu.
Podobnie z wypisywaniem kodu ASCII znaku:
char znak;
int kod;
kod = znak;
cout << kod << endl;
Jest to szczególny przykład tzw. konwersji typów (inaczej: rzutowania typów), czyli zmiany jednego typu na inny. W przypadku typów całkowitych w C++ dokonuje się ona automatycznie, w momencie przypisania zmiennej jednego typu wartości zmiennej (bądź wyrażenia) innego typu. Można też jawnie "poprosić" kompilator C++, aby dokonał konwersji. Robi się to, umieszczając nazwę typu przed zmienną:
char znak;
cout << (int)znak << endl;
Trochę więcej o konwersjach opowiemy w następnych lekcjach. Tymczasem jeszcze jeden, "złośliwy" przykład. Powiedzmy, że chcielibyśmy napisać program, który wczyta liczbę \(i\) i wypisze \(i\)-tą małą literę alfabetu angielskiego (a zatem \(i \in \{1,\ldots,26\}\), alfabet angielski ma bowiem 26 liter). Moglibyśmy to próbować zrobić tak:
int numer;
cin >> numer;
cout << 'a' + numer - 1;
Gdy na wejściu wprowadzimy np. liczbę 3, na wyjściu otrzymamy... liczbę 99, czyli kod ASCII litery c
! Jest tak dlatego, że wyniki działań arytmetycznych są w C++ domyślnie interpretowane jako liczby (w tym przypadku typu int
). Aby otrzymać na wyjściu rzeczywiście literę c
, musimy użyć konwersji na typ char
:
int numer;
cin >> numer;
cout << (char)('a' + numer - 1);
Zadania
W tej lekcji, oprócz trzech "standardowych" zadań mamy również przygotowane jedno "z gwiazdką", nieco trudniejsze. Jeśli nie potrafisz go jeszcze rozwiązać, nie przejmuj się!