Notka dziesiąta, w której poznajemy zmienne znakowe.

Zadanie 8: oRtOgRaFiA

– Tomuś! – krzyknęła pani przedszkolanka – Słowa zaczynamy wielkimi literami, a potem używamy małych liter! Ile razy mam ci to powtarzać? Za karę przepiszesz cały tekst na nowo.

bIedNy toMeK. pomÓż Mu wYkonAć to zAdAnie.

Wejście

W pierwszej i jedynej linii wejścia znajduje się słowo złożone z małych i wielkich liter alfabetu angielskiego. Słowo składa się z co najmniej jednego znaku, ale z nie więcej niż 100.

Wyjście

Na standardowym wyjściu należy wypisać słowo z wejścia sformatowane w taki sposób, aby jedynie pierwsza litera była wielka, a resztę słowa tworzyły małe litery.

Przykład

Dla danych wejściowych

teRRoRystka

Poprawną odpowiedzia jest

Terrorystka

Kolejna lekcja i kolejne zadanie przed nami. Już na starcie widzimy, że mamy drobny problem. Nauczyliśmy się do tej pory wczytywać z wejścia jedynie liczby naturalne. A tu mamy do czynienia ze słowami. Jak sobie z tym poradzić?

Zacznijmy od tego, jak w ogóle komputery zapamiętują słowa. Powiedzieliśmy sobie, że komputery operują na bitach. Czyli na zerach i jedynkach. Jak w takim razie zapamiętują inne rzeczy: muzykę, filmy czy napisy? Do tego celu komputery używają kodowania. Z kodowaniem mamy do czynienia wtedy, gdy ustalamy, że taki a taki ciąg bitów1 będzie oznaczał to i to. Dla przykładu: ciąg 01100001 będzie oznaczał na przykład literę a. Z lekcji szóstej wiemy, że 01100001 oznacza w systemie binarnym liczbę 97. Będę zatem pisał 97 zamiast 01100001 bo 01100001 pisze się bardzo długo i jeśli jeszcze raz będę musiał napisać 01100001 to rzucę wszystko i wyjadę w Bieszczady.

1 – jeśli mówimy o komputerach to zazwyczaj będziemy mieli na myśli ciąg bitów – ale nie zawsze tak musi być. Jeśli na przykład weźmiemy ciąg kresek, to będziemy mieli do czynienia na przykład z kodem kreskowym. Jeśli przyporządkujemy numery do nazw ulic (#nadmierneuproszczenie #proszeniewyzywacwkomentarzach) to będziemy mieli do czynienia z kodem pocztowym.

Nas dziś będą interesować kody ASCII. Kody ASCII wyglądają następująco:

kodznakkodznakkodznak
32(spacja)64@96`
33!65A97a
3466B98b
35#67C99c
36$68D100d
37%69E101e
38&70F102f
3971G103g
40(72H104h
41)73I105i
42*74J106j
43+75K107k
44,76L108l
4577M109m
46.78N110n
47/79O111o
48080P112p
49181Q113q
50282R114r
51383S115s
52484T116t
53585U117u
54686V118v
55787W119w
56888X120x
57989Y121y
58:90Z122z
59;91[123{
60<92\124|
61=93]125}
62>94^126~
63?95_127(delete)

Nie. Nie musisz zapamiętywać tej tabelki. Nie będzie jej na sprawdzianie. W ogóle żadnego nawet sprawdzianu nie będzie. Rzeczy które powinieneś zapamiętać to:

  • Wszystkie cyfry 0 – 9 są w kodzie ASCII wymienione po kolei w kolejności rosnącej
  • Wszystkie litery A – Z są w kodzie ASCII wymienione po kolei w kolejności alfabetycznej
  • Wszystkie litery a – z są w kodzie ASCII wymienione po kolei w kolejności alfabetycznej
  • Wielka litera od swojego odpowiednika w postaci małej litery różni się o 32
  • (z gwiazdką dla chętnych) kod ASCII 32 oznacza spację

Ktoś mądry tak wymyślił kod ASCII, że wszystkie litery i cyfry są po kolei. Jeśli 2 ma kod 50, to 3 ma kod 51. Jeśli a ma kod 97 to b ma kod 98 itd. Jest to niezwykle ważna własność, z której będziemy korzystać. Gdyby kod ASCII nie miał tej własności, to świat byłby bardzo smutny.

Innym powodem dla którego nie warto zapamiętywać kodów ASCII jest to, że łatwo napisać program, który kody ASCII nam przypomni. Właściwie to napiszmy go teraz!

#include <iostream>

using namespace std;

int main()
{
    for (int i = 32; i < 128; i++)
    {
        cout << i << " " << char(i) << endl;
    }

    return 0;
}

Dużo łatwiej zapamiętać niż taką potężną tabelkę. Programowanie jest fajne!

W powyższym kodzie użyliśmy po raz pierwszy rzutowania typu (zmiana typu, konwersja typu). Instrukcja char(i) zamienia zmienną i z typu int na typ char. Będziemy z tego typu konstrukcji korzystać w przyszłości. O ile zamiana jednej zmiennej typu całkowitoliczbowego na inny typ całkowitoliczbowy nie budzi naszych obaw, to poznaliśmy jeszcze typ logiczny. Z typem logicznym sprawa ma się następująco. Jeśli spróbujemy rzutować wartość true na typ liczbowy – otrzymamy 1. Przy rzutowaniu false na typ liczbowy otrzymamy 0. Przy rzutowaniu 0 na typ logiczny – otrzymamy false i przy rzutowaniu dowolnej innej liczby różnej od 0 na typ logiczny – otrzymamy true. Zapamiętajcie – będziemy korzystać.

Powiedziałem, że char(i) zamienia zmienną i z typu int na typ char. Ale chwila chwila, zawoła nie jeden z was – co to jest typ char? Oj! Ktoś musiał spać gdy czytał notkę szóstą! Typ char to typ całkowitoliczbowy 8-bitowy. Jednak powszechnie używany jest w C++ do kodowania znaków za pomocą kodów ASCII. Dlatego na przykład poniższy program, zamiast wypisać na ekranie liczbę 97, wyświetli literę a:

#include <iostream>

using namespace std;

int main()
{
    char litera = 97;

    cout << litera << endl;

    return 0;
}

Nie piszmy jednak char litera = 97; Piszmy jak ludzie:

#include <iostream>

using namespace std;

int main()
{
    char litera = 'a';

    cout << litera << endl;

    return 0;
}

Ten kod różni się od poprzedniego jedynie tym, że jest czytelniejszy. Jeśli po godzinie (dniu? miesiącu?) wróciłbyś do poprzedniego kodu, to zapomniałbyś już, jaka litera zostanie wyświetlona, bo nie pamiętałbyś, że 97 oznacza literę a (ja na pewno bym zapomniał). Z drugim programem nie ma takiego problemu. Piszemy w nim, że char litera = 'a'; co jest dokładnie tym samym, ale nie ma obaw, że zapomnimy co to oznacza albo, co ważniejsze, że się pomylimy pisząc taki program. Zwróćcie uwagę, że znaki w języku C++ umieszczamy w pojedynczych cudzysłowach. Rodzi się pytanie. A co jeśli pojedynczy cudzysłów chcielibyśmy podstawić pod zmienną litera? Posłuży nam do tego sekwencja ucieczki (nie patrzcie na mnie – nie ja tę nazwę wymyślałem):

#include <iostream>

using namespace std;

int main()
{
    char litera = '\'';

    cout << litera << endl;

    return 0;
}

Inne sekwencje o których powinniście być świadom:

  • \n – znak nowej linii
  • \t – znak tabulatora
  • \” – podwójny cudzysłów
  • \\ – ukośnik wsteczny \

Uff… to prawie wszystko. Zostało już tylko najważniejsze – z punktu widzenia programisty, znak to wciąż liczba (kod) i możecie jej używać tak jak liczby. Możecie je dodawać, odejmować a także porównywać. Na przykład poniższy kod rozpozna czy ma do czynienia z literą czy z cyfrą:

#include <iostream>

using namespace std;

int main()
{
    char znak = '5';

    if ('A' <= znak and znak <= 'Z')
    {
        cout << "Wielka litera" << endl;
    }
    else if ('a' <= znak and znak <= 'z')
    {
        cout << "Mała litera" << endl;
    }
    else if ('0' <= znak and znak <= '9')
    {
        cout << "Cyfra" << endl;
    }
    else
    {
        cout << "Inny znak" << endl;
    }

    return 0;
}

Program tworzy zmienną znak typu char i przypisuje jej wartość 53 (cyfra 5 w kodzie ASCII). Następnie pierwszy if sprawdza czy wartość zmiennej znak jest z przedziału [65; 90]. Jeśli tak, to wypisuje na ekran "Wielka litera". Jeśli nie, to sprawdza czy znak jest wartością z przedziału [97; 122]. Jeśli jest, to wypisuje "Mała litera". W końcu sprawdza, czy jest to wartość z przedziału [48; 57]. Jeśli tak, to wypisuje "Cyfra" a jeśli nie, to "Inny znak". Zwróćcie uwagę, że pisząc ten kod nie musiałem pamiętać jaki kod mają poszczególne znaki. Skorzystałem jedynie z tego, że pomiędzy 'A' a 'Z' w kodzie ASCII występują jedynie wszystkie wielkie litery alfabetu angielskiego.

To tyle jeśli chodzi o znaki. Porozmawiajmy teraz o słowach. Słowo to tablica znaków. To prawie wszystko co musisz o nich wiedzieć.

#include <iostream>

using namespace std;

int main()
{
    char imie[10];

    cout << "Hej! Jak masz na imię?" << endl;
    cin >> imie;
    cout << "Cześć " << imie << "! Miło Cię poznać!" << endl;

    return 0;
}

Powyższy program wypisuje powitanie i prosi użytkownika o podanie imienia. Następnie pozdrawia go, używając jego imienia. W programie użyto tablicę potrafiącą przechowywać 10 znaków. Jeśli ktoś ma imię złożone z większej liczby liter – program może ulec awarii. Przykład użycia:

Hej! Jak masz na imię?
Krzyś
Cześć Krzyś! Miło Cię poznać!

Jak widać – Krzyś składa się z 5 liter. Nie musimy użyć wszystkich 10 znaków w tablicy. Ale w takiej sytuacji musimy pamiętać ile liter użyliśmy. Z podobną sytuacją mieliśmy do czynienia w poprzedniej notce. Wtedy użyliśmy zmiennej do zapamiętania ile elementów faktycznie użyliśmy. Istnieje jednak jeszcze inny sposób i ten sposób jest wykorzystywany przy napisach w C++. Mianowicie napis kończy się specjalnym znakiem oznaczającym…. koniec napisu. Tym symbolem jest '\0' i jego kod ASCII to 0. Jest niesłychanie ważne, aby o tym znaku pamiętać rezerwując pamięć dla tablicy. I z tego też powodu, w poprzednim programie jednak tablica potrafi przechować 9 znaków (+ znak końca napisu). Jeśli ktoś ma imię złożone z większej liczby liter może spowodować awarię programu.

Ostatnią rzeczą jest inicjowanie tablicy znaków. Do tego celu używa się podwójnego cudzysłowu:

#include <iostream>

using namespace std;

int main()
{
    char imie[10] = "komputer";

    cout << "Hej! Mam na imię " << imie << "." << endl;

    return 0;
}

Wiemy już wszystko, co jest potrzebne do rozwiązania naszego zadania.

#include <iostream>

using namespace std;

int main()
{
    char slowo[101]; // 100 elementów na słowo + 1 na znak '\0'

    cin >> slowo;

    if ('a' <= slowo[0] and slowo[0] <= 'z')
    {
        slowo[0] -= ' ';
    }

    for (int i = 1; slowo[i]; i++)
    {
        if ('A' <= slowo[i] and slowo[i] <= 'Z')
        {
            slowo[i] += ' ';
        }
    }
    cout << slowo << '\n';

    return 0;
}

Starałem się tak napisać program, aby umieścić w nim jak najwięcej elementów które dzisiaj poznaliśmy. Tworzymy tablicę w której będziemy przechowywać nasze słowo. Ma ona 101 elementów – 100 na znaki (bo wiemy z treści zadania, że słowo będzie miało nie więcej niż 100 znaków) + jeden element na znak końca słowa. Następnie wczytujemy słowo. Sprawdzamy czy pierwszy znak jest małą literą. Jeśli tak to zamieniamy ją na wielką. Aby zamienić małą literą na wielką – musimy odjąć od niej 32. Moglibyśmy zrobić to normalnie pisząc slowo[0] -= 32; ale jesteśmy porąbani i pamiętamy, że 32 w kodzie ASCII oznacza spację… więc od małej litery odejmujemy spację i otrzymujemy wielką literę – logiczne!

Następnie mamy pętlę for, która zaczyna od drugiego znaku (i = 1) i kończy… hmmm… no właśnie kiedy? Użyłem tutaj kolejnej niepotrzebnej sztuczki, żeby pokazać jaki jestem fajny. Powinniśmy zatrzymać się kiedy napotkamy znak '\0'. Zatem warunek powinien wyglądać tak, że slowo[i] != '\0' co jest równe true jeśli slowo[i] jest symbolem różnym od '\0' i równe false jeśli jest symbolem równym '\0'. Ale! Ponieważ C++ oczekuje w tym miejscu wartości logicznej, a nie wartości całkowitoliczbowej – stosowane jest rzutowanie. A jak wiemy rzutowanie z wartości całkowitoliczbowej na wartość liczbową działa w taki sposób, że jeśli wartość jest równa 0 zwracana jest wartość false a jeśli wartość jest różna od zera – wartość true. Teraz wartość 0 w kodzie ASCII oznacza właśnie symbol końca słowa – '\0'. Zatem jeśli slowo[i] jest symbolem równym '\0' to zwrócimy true, a jeśli jest symbolem różnym od '\0' to zwrócimy false. Dokładnie tak jak chcieliśmy!

Dalej w kodzie robimy rzecz odwrotną do wcześniejszej – sprawdzamy czy mamy do czynienia z wielką literą i jeśli tak to zamieniamy ją na małą. Jeśli będziecie mieli problemy z zapamiętaniem kiedy trzeba spację dodać a kiedy trzeba spację odjąć – pozwalam wam wyjątkowo użyć operatora, którego jeszcze nie poznaliśmy. Instrukcja litera ^= ' '; zamieni wielkość litery, to znaczy jeśli litera była mała to teraz będzie wielka a jeśli była wielka to teraz będzie mała. Co to jest za operator i dlaczego tak działa powiemy sobie w notce o operatorach binarnych.

Na końcu wypisujemy słowo na ekran i wypisujemy znak nowej litery. Normalnie do tego celu korzystaliśmy z endl, który robi prawie to samo (różni się drobnym szczegółem, którym nie musicie sobie teraz zaprzątać głowę). To co? Zadanka?

Zadania do samodzielnego rozwiązywania

  1. Translation (ranking 800)
  2. Word (ranking 800)
  3. Way too long words (800)
  4. Chat room (1000)
  5. BA-String (1800)

Nie. Tablic A i B nie możecie porównywać if (A == B). Dlaczego, dowiecie się dopiero jeśli zrobię notkę o wskaźnikach. Aby sprawdzić czy elementy tablicy A są równe elementom tablicy B musicie użyć pętli for.

Jak widzicie, zadanie BA-String jest istotnie trudniejsze od wszystkich pozostałych. Ale wszystko co potrzebujecie do rozwiązania tego zadania, było już omawiane w tej i poprzednich notkach.

Back To Top