Kurs: Kurs wstępu do programowania

Lekcja: Instrukcja przypisania i typ znakowy char

Część programistyczna: Instrukcja przypisania

W tej lekcji opowiemy o innej metodzie nadawania wartości zmiennej poza jej wczytaniem. Jest nią instrukcja przypisania. Aby nadać zmiennej \(a\) typu int wartość 5, możemy użyć następującej instrukcji przypisania:

a = 5;
Przypisania tego typu zazwyczaj stosuje się 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. Przykładowo, jeśli wczytaliśmy wartości zmiennych całkowitych \(a\), \(x\) oraz \(b\), możemy obliczyć wartość funkcji liniowej \(ax+b\) i przypisać ją do zmiennej \(y\):

int a, b, x, y;
cin >> a >> x >> b;
y = a * x + b;
W ten sposób wprowadza się zmienną pomocniczą, co nierzadko pomaga uprościć obliczenia. W ten sposób w algorytmie rozwiązywania równania kwadratowego (patrz komentarz do poprzedniej lekcji) moglibyśmy wprowadzić zmienną pomocniczą \(Delta\).

Co ciekawe, zmienna, której przypisujemy nową wartość, może wystąpić także w wyrażeniu po prawej stronie instrukcji przypisania! Np. w takiej instrukcji:

a = a + 3;
Jak to rozumieć? W instrukcji przypisania najpierw obliczana jest wartość wyrażenia po prawej stronie (tu: \(a+3\)), a następnie staje się ona nową wartością zmiennej po lewej stronie (tu: \(a\)). Tak więc powyższe przypisanie odpowiada zwiększeniu wartości zmiennej \(a\) o 3.

Zmiennym możemy wielokrotnie przypisywać różne wartości. To uzasadnia, dlaczego nazywamy je właśnie zmiennymi.

Czas na przykład. W znakomitej książce Mozaika matematyczna pod redakcją Endre Hodi (Wiedza Powszechna, Warszawa 1987) można znaleźć opis następującej zabawy-zgadywanki.


Zobacz tekst nagrania

Bardziej złożony przykład

W tym przykładzie wykorzystamy większość elementów języka C++, których nauczyliśmy się do tej pory.


Zobacz tekst nagrania

Komentarz: Reprezentacja danych wejściowych

Czasami kłopoty ze sformułowaniem algorytmu wynikają z niedoprecyzowania postaci danych wejściowych. Oto przykład zadania zaczerpnięty z notatek Piotra Chrząstowskiego ze "Wstępu do programowania" dla studentów I roku informatyki na Uniwersytecie Warszawskim.


Zobacz tekst nagrania

Warto także postawić sobie pytanie, jak zmieniłoby się rozwiązanie zadania, gdybyśmy wybrali inną reprezentację danych wejściowych. Na przykład, co by było, gdyby prostokąty były reprezentowane razem z brzegiem?

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:

kodznakkodznakkodznakkodznakkodznak
0-31znaki specjalne51371G91[111o
32spacja52472H92\112p
33!53573I93]113q
34"54674J94^114r
35#55775K95_115s
36$56876L96`116t
37%57977M97a117u
38&58:78N98b118v
39'59;79O99c119w
40(60<80P100d120x
41)61=81Q101e121y
42*62>82R102f122z
43+63?83S103g123{
44,64@84T104h124|
45-65A85U105i125}
46.66B86V106j126~
47/67C87W107k127znak specjalny
48068D88X108l
49169E89Y109m
50270F90Z110n

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\}\), bowiem alfabet angielski ma 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

Oto zadania do dzisiejszej lekcji. W tej lekcji mamy również przygotowane jedno zadanie z gwiazdką. Przypominamy, że punkty za zadania z gwiazdką nie wpływają na zaliczenie kursu (tym bardziej, że Wielkanoc jest jeszcze daleko).