PROGRAMOWANIE – PRZYKŁADY
IDE C++ WinBGI C# Wpadki

Przykład C++

Kalendarz miesięczny Struktury i wskaźniki Moduł czytania znaku Program w Borland C++ Obsługa okna konsoli Program w MinGW C++ Program w Visual C++ Poprzedni przykład Następny przykład Program w Visual C# Kontakt

Kalendarz miesięczny

Numery dni w kalendarzu miesięcznym są zazwy­czaj zesta­wione w siedmiu kolu­mnach lub wier­szach odpowia­dających kolejnym dniom tygodnia opisanych nazwami lub ich skrótami. Na przy­kład wrzesień 2018 roku może być przedsta­wiony w ukła­dzie kolu­mnowym nastę­pująco:

     Wrzesień 2018
  Nd Po Wt Śr Cz Pt So
                     1
   2  3  4  5  6  7  8
   9 10 11 12 13 14 15
  16 17 18 19 20 21 22
  23 24 25 26 27 28 29
  30

Omówiony w poprzednim przykładzie C++ moduł obliczeń kalenda­rzowych kalend ułatwia zbudo­wanie programu wyświe­tlania kalen­darza miesię­cznego, pozwala bowiem ustalić, ile dni ma dany miesiąc (funkcja dmax) i jaki dzień tygodnia go rozpo­czyna (funkcja dtyg). Wiadomo więc, od której kolumny zacząć wypisy­wanie numerów dni i na jakim numerze zakończyć. Oto wstępna wersja funkcji miesiac ukazu­jącej kalen­darz dla miesiąca m roku r:

string Mc[] = {"Styczen", "Luty", "Marzec", "Kwiecien", "Maj", "Czerwiec", "Lipiec",
               "Sierpien", "Wrzesien", "Pazdziernik", "Listopad", "Grudzien"};

void miesiac(int m, int r)
{
    cout << setw((17 - Mc[m-1].length()) / 2) << "" << Mc[m-1] << setw(5)
         << r << "\n Nd Po Wt Sr Cz Pt So\n";
    int n = dtyg(1, m, r), max = dmax(m, r);
    if (n > 0)
        cout << setw(3 * n) << "";
    for (int d = 1; d <= max; d++)
    {
        cout << setw(3) << d;
        if ((d + n) % 7 == 0)
            cout << endl;
    }
}

Najpierw wypisany zostaje nagłówek z nazwą miesiąca i skrótami nazw dni tygodnia. Metoda length zwracająca długość łańcucha C++ została użyta w wyra­żeniu wylicza­jącym wielkość odstępu potrze­bnego do wycentro­wania pierw­szego wiersza nagłówka zależną od długości nazwy miesiąca. Numery dni są wypi­sywane w pętli for do pól o szero­kości 3 znaków. Przed pętlą oblicza się, w jakim dniu tygodnia przypada pierwszy dzień miesiąca (wartość zmiennej n) i ile dni ma miesiąc (wartość zmiennej max). Jeżeli pierwszym dniem miesiąca nie jest niedziela, tworzy się odpowie­dniej szero­kości odstęp, aby jedynka znalazła się dokładnie pod skrótem nazwy pierw­szego dnia miesiąca. Po każdym numerze odpowia­dającym sobocie następuje przejście na początek nastę­pnego wiersza, miejsca zarezerwo­wanego dla numeru przypi­sanego niedzieli.

Niewątpliwą zaletą programu byłoby, aby rozpo­czynał działanie od wyświe­tlenia w oknie konsoli bieżą­cego miesiąca i umożli­wiał przecho­dzenie do kolejnych miesięcy za pomocą klawiszy strzałek (góra, dół). Natu­ralnym wydaje się też, by wybór klawisza Esc kończył wyko­nanie programu.

Struktury i wskaźniki

Wyświetlenie kalendarza bieżącego miesiąca wymaga znajomości aktualnej daty. W środo­wisku Borland C++ najwygo­dniej jest pobrać ją z systemu opera­cyjnego, wywo­łując funkcję getdate, której argu­mentem jest wskaźnik na zmienną (adres pamięci przydzie­lonej zmiennej) typu date zdefinio­wanego w pliku nagłów­kowym dos.h nastę­pująco:

struct date
{
    int    da_year;   /* Year - 1980 */
    char   da_day;    /* Day of the month */
    char   da_mon;    /* Month (1 = Jan) */
};

Typ date jest strukturą złożoną z trzech pól: da_year (numer roku), da_day (numer dnia) i da_mon (numer miesiąca). Gwoli ścisłości, komentarz objaśniający rolę pola da_year jest mylący! Wartością tego pola uzyskaną za pomocą funkcji getdate jest w istocie pełny numer roku, a nie numer roku pomniej­szony o 1980. Warto zwrócić uwagę, że po nawiasie klamrowym zamyka­jącym defi­nicję struktury wystę­puje średnik. Jest on konieczny, gdyż bezpo­średnio po takiej definicji można w razie potrzeby zamieścić listę zmiennych i wska­źników definio­wanego typu.

W przeciwieństwie do tablicy, która jest agregatem ele­mentów tego samego typu poindekso­wanych od zera wzwyż, elementy struktury, nazywane polami, mogą być dowolnego typu. Dostęp do nich umożliwia konstru­kcja złożona z nazwy struktury, kropki i nazwy pola. Na przykład kod

date dt;
dt.da_year = 2018;
dt.da_mon = 9;
dt.da_day = 24;

powoduje przypisanie daty 24.09.2018 zmiennej dt typu date, a kod

date dt;
getdate(&dt);
miesiac(dt.da_mon, dt.da_year);

może stanowić początek funkcji main programu, który zamie­rzamy zbudować, ponieważ po pobraniu przez funkcję getdate aktu­alnej daty do zmiennej dt wyświetla bieżący miesiąc. Podobnie jak w funkcji scanf, wyra­żenie &dt będące argu­mentem (parame­trem) wywo­łania funkcji getdate jest wskaźni­kiem na zmienną dt.

Jeżeli znany jest wskaźnik na strukturę, dostęp do niej wymaga użycia opera­tora * (gwiazdka), zaś do jej pól albo opera­torów gwiazdki i kropki, albo opera­tora -> (strzałka złożona z dwóch znaków). Na przykład wyszcze­gólniony wyżej kod przypi­sujący zmiennej dt datę 24.09.2018 można zastąpić z użyciem wskaźni­ka p kodem

date dt, *p = &dt;
(*p).da_year = 2018;
(*p).da_mon = 9;
(*p).da_day = 24;

bądź

date dt, *p = &dt;
p->da_year = 2018;
p->da_mon = 9;
p->da_day = 24;

Nawiasy w pierwszym przypadku są niezbędne, gdyż opera­tor kropki ma wyższy priorytet niż opera­tor gwiazdki, toteż najpierw trzeba dostać się do stru­ktury *p, a potem do jej składowych. Wydaje się, że zapis z użyciem opera­tora strzałki jest bardziej czytelny.

Moduł czytania znaku

Naciśnięcie klawisza sterowania kursorem lub klawisza funkcyj­nego, a także ich kombi­nacji z klawi­szami Alt i Ctrl powoduje umie­szczenie w buforze klawia­tury nie jednego, lecz dwóch znaków. Pierwszy z tej pary ma kod 0 lub 224 (w Borland C++ zawsze 0), zaś drugi różny od zera. Na przykład dla klawisza Delete w buforze poja­wiają się dwa znaki, pierwszy o kodzie 0 (Borland C++) lub 224 (MinGW C++ i Visual C++), drugi o kodzie 83. A oto prosty program pozwa­lający prześle­dzić kody znaków wprowa­dzanych z kla­wiatury (jego wykonanie kończy znak Esc o kodzie 27):

#include <stdio.h>
#include <conio.h>

int main()
{
    int c;
    while ((c = _getch()) != 27)                   // Dopóki pobrany znak jest różny od ESC:
    {                                              //---------------------------------------
        printf("%3d:", c);                         // Drukuj kod znaku.
        if (c == 0 || c == 224)                    // Jeśli jest to znak sterujący,
            printf(" drugi znak: %d\n", _getch()); // pobierz kolejny znak i drukuj jego kod.
        else                                       // W przeciwnym razie
            printf(" %c\n", c > ' ' ? c : ' ');    // drukuj znak (gdy drukowalny) lub spację.
        while (_kbhit())                           // Opróżnij bufor (w Borland C++ kbhit),
            _getch();                              // pobierając z niego kolejne znaki.
    }
    return 0;
}

W celu ułatwienia rozpoznania naciśniętego klawisza zdefi­niujemy własną funkcję czytania znaku, której wartością będą liczby całko­wite typu int. W przy­padku zwykłego klawisza wynikiem wywołania funkcji będzie kod znaku odpowia­dającego temu klawi­szowi, czyli liczba mniejsza od 256, a w przy­padku klawisza funkcyj­nego, stero­wania kursorem lub kombi­nacji generu­jących podwójne znaki – kod drugiego znaku powię­kszony o liczbę 256. Funkcję tę i stałe oznacza­jące jej wybrane wartości o kodach wyższych od 256 umieścimy w odrę­bnym module o nazwie klaw. Jego plik nagłów­kowy może wyglądać następu­jąco:

// klaw.h - Moduł czytania znaku z klawiatury w trybie tekstowym
// -------------------------------------------------------------
// czytajZnak - pobiera znak z klawiatury i zwraca jego kod
// -------------------------------------------------------------

#ifndef H_KLAW
#define H_KLAW

#include <conio.h>

#define K_UP    (256 + 72)   // Strzałka w górę
#define K_DN    (256 + 80)   // Strzałka w dół
#define K_PGUP  (256 + 73)   // PgUp
#define K_PGDN  (256 + 81)   // PgDn
#define K_LEFT  (256 + 75)   // Strzałka w lewo
#define K_RIGHT (256 + 77)   // Strzałka w prawo
#define K_HOME  (256 + 71)   // Home
#define K_END   (256 + 79)   // End
#define K_TAB   9            // Tab
#define K_BACKS 8            // Backspace
#define K_ENTER 13           // Enter
#define K_ESC   27           // Esc

int czytajZnak();            // Czytanie znaku

#endif // H_KLAW

Druga część modułu zawarta w pliku klaw.cpp zawiera jedną dyrektywę #include i definicję funkcji czytajZnak, której prototyp znajduje się w przedsta­wionym wyżej pliku nagłów­kowym:

#include "klaw.h"

int czytajZnak()
{
    int c = _getch();
    return (c != 0 && c != 224) ? c : 256 + _getch();
}

Program w Borland C++

Zgodnie z wcześniejszymi ustaleniami przyjmijmy, że na początku program pobiera z systemu bieżącą datę za pomocą funkcji getdate i wyświe­tla aktualny miesiąc, korzy­stając z funkcji miesiac. Następnie w pętli wczytuje kolejny znak z klawiatury i inter­pretuje go nastę­pująco:

Czytanie znaku za pomocą funkcji czytajZnak i porównanie go ze stałą K_ESC można umieścić w warunku konty­nuacji pętli while, a konstru­kcję wyboru nastę­pnego lub poprze­dniego miesiąca można zaprogra­mować za pomocą trójwarian­towej instrukcji if—else—if:

while ((c = czytajZnak()) != K_ESC)
{
    if (c == K_DN)
    {
        ...              // następny miesiąc
    }
    else if (c == K_UP)
    {
        ...              // poprzedni miesiąc
    }
}

Kod wczytanego znaku jest kolejno porówny­wany ze stałymi K_ESC, K_DNK_UP. Gdy jest różny od K_ESC, wykonana będzie tylko instru­kcja po pierw­szym speł­nionym porównaniu z K_DN lub K_UP, zaś w przypadku niezgo­dności kodu z obu stałymi, żadna akcja wobec rozpatry­wanego znaku nie zostanie podjęta. Skrajne nawiasy {} można pominąć, jednak pozosta­wienie ich ułatwia zrozu­mienie kodu źródłowego.

Poniżej zaprezentowana jest pełna wersja programu wyświetla­jącego w oknie konsoli kalendarz miesięczny. Porównując funkcję miesiac z jej pierwo­wzorem, można zauważyć drobne zmiany i uzupeł­nienia. I tak, nagłówek z nazwą miesiąca został nieco rozbu­dowany (podkre­ślenie), a umieszczenie łań­cucha pustego na początku tablicy Mc pozwoliło na natu­ralną numerację miesięcy od 1 do 12 zamiast od 0 do 11. Ponadto posłu­żono się funkcjami clrscrgotoxy z biblio­teki conio. Pierwsza czyści okno konsoli, co jest istotne z uwagi na przecho­dzenie do różnych miesięcy, zaś druga przenosi kursor do pozycji znakowej 3 wier­sza 11 okna, by wyświe­tlić od tego miejsca krótkie menu informu­jące użytko­wnika, jak ma sterować programem.

#include <iostream>
#include <iomanip>
#include <dos.h>
#include "kalend.h"
#include "klaw.h"

using namespace std;

const string Mc[] = {"",
    "Styczen", "Luty", "Marzec", "Kwiecien", "Maj", "Czerwiec", "Lipiec",
    "Sierpien", "Wrzesien", "Pazdziernik", "Listopad", "Grudzien"};

void miesiac(int m, int r)
{
    clrscr();
    cout << setw((17 - Mc[m].length()) / 2) << "" << Mc[m] << setw(5)
         << r << "\n --------------------\n Nd Po Wt Sr Cz Pt So\n";
    int n = dtyg(1, m, r), max = dmax(m, r);
    if (n > 0)
        cout << setw(3 * n) << "";
    for (int d = 1; d <= max; d++)
    {
        cout << setw(3) << d;
        if ((d + n) % 7 == 0)
            cout << endl;
    }
    gotoxy(3, 11);
    cout << "Menu: Up, Dn, Esc";
}

int main()
{
    date dt;
    getdate(&dt);
    miesiac(dt.da_mon, dt.da_year);
    int c;
    while ((c = czytajZnak()) != K_ESC)
    {
        if (c == K_DN)
        {
            if (dt.da_mon++ < 12)
                miesiac(dt.da_mon, dt.da_year);
            else
                miesiac(dt.da_mon = 1, ++dt.da_year);
        }
        else if (c == K_UP)
        {
            if (--dt.da_mon > 0)
                miesiac(dt.da_mon, dt.da_year);
            else
                miesiac(dt.da_mon = 12, --dt.da_year);
        }
    }
}

Specyficzną cechą języków C i C++, prowadzącą do zwartego i zarazem efekty­wnego kodu, jest możliwość umieszczania wyrażeń przypisu­jących w innych wyraże­niach. Takie skróty występują m.in. w kodzie funkcji main opisującym przejście od grudnia do stycznia nastę­pnego roku i od stycznia do grudnia poprze­dniego roku. Szcze­gólnie ważny bywa kontekst, w którym wystę­pują opera­tory zwięk­szania (++) i zmniej­szania (--), gdyż mogą one wystąpić zarówno przed zmienną, jak i po niej, wpływając w dwójnasób na wartość wyrażenia. Na przykład w wyrażeniu

dt.da_mon++ < 12

numer miesiąca zostaje zwiększony o 1 po wykorzy­staniu, czyli po porówna­niu z 12, zaś w wyrażeniu

--dt.da_mon > 0

numer miesiąca zostaje zmniejszony o 1 przed wykorzystaniem, czyli przed porówna­niem z zerem.

Obsługa okna konsoli

Biblioteka conio kompilatora Borland C++ 5.5 zawiera szereg funkcji obsługi okna konsolo­wego, które nie występują w biblio­tekach kompila­torów MinGW C++ i Visual C++. Na przykład w biblio­tekach conio nowszych kompila­torów nie ma funkcji clrscrgotoxy. Oznacza to, że przedsta­wiony powyżej program nie jest przenośny. Można temu zaradzić, dodając do niego kod obu braku­jących funkcji. Na użytek nie tylko tego, ale i kilku innych programów prezento­wanych w dalszej części niniejszej witryny, opraco­wano w Windows API moduł bconio zawie­rający definicje nazw kolorów i implemen­tacje wybranych funkcji Borland conio:

Nazwa koloru Numer Opis Nazwa koloru Numer Opis
BLACK 0         – czarny DARKGRAY 8         – ciemnoszary
BLUE 1         – niebieski LIGHTBLUE 9         – jasnoniebieski
GREEN 2         – zielony LIGHTGREEN 10         – jasnozielony
CYAN 3         – turkusowy LIGHTCYAN 11         – jasnoturkusowy
RED 4         – czerwony LIGHTRED 12         – jasnoczerwony
MAGENTA 5         – fioletowy LIGHTMAGENTA 13         – jasnofioletowy
BROWN 6         – brązowy YELLOW 14         – żółty
LIGHTGRAY 7         – jasnoszary WHITE 15         – biały
Nazwa funkcji Opis
clrscr Czyści okno tekstowe i ustawia kursor na pozycji znakowej 1,1 (lewy górny róg)
clreol Czyści wiersz tekstu od pozycji kursora do końca wiersza
delline Usuwa cały wiersz w pozycji kursora, przesuwając resztę w górę
gotoxy Przenosi kursor do pozycji znakowej określonej w parametrach x, y
insline Wstawia pusty wiersz w pozycji kursora, przesuwając resztę w dół
setcursor Dla parametru false ukrywa kursor, a dla true pokazuje go (w Borland conio istnieje funkcja _setcursortype ustawiająca rozmiar kursora)
textbackground Ustawia kolor tła określony w parametrze (standardowo tło jest czarne)
textcolor Ustawia kolor tekstu określony w parametrze (standardowo tekst jest jasnoszary)
wherex Zwraca pozycję poziomą kursora tekstowego
wherey Zwraca pozycję pionową kursora tekstowego

Program w MinGW C++

Próba przeniesienia programu kalendarza miesięcznego w Borland C++ do kompila­tora MinGW C++ przy wykorzy­staniu modułu bconio kończy się niepowo­dzeniem. Przyczyną jest brak w środo­wisku MinGW C++ pliku nagłów­kowego dos.h, a tym samym defi­nicji struktury date i funkcji getdate. Obie defi­nicje możemy wstawić do programu, dzięki czemu unikniemy modyfi­kacji funkcji miesiacmain. Pierwsza defi­nicja jest znana i łatwo ją przepisać, drugą trzeba napisać np. w oparciu o funkcje timelocaltime, których prototypy znajdują się w pliku nagłów­kowym time.h języka C lub ctime języka C++. Pierwsza zwraca w zmiennej typu time_t (32 bity) czas syste­mowy w sekundach liczony od 1 sty­cznia 1970 roku, druga konwer­tuje go na strukturę

struct tm
{
    int   tm_sec;     // Sekundy: 0 ÷ 59
    int   tm_min;     // Minuty: 0 ÷ 59
    int   tm_hour;    // Godziny: 0 ÷ 23
    int   tm_mday;    // Dzień miesiąca: 1 ÷ 31
    int   tm_mon;     // Miesiąc: 0 ÷ 11
    int   tm_year;    // Rok liczony od 1900 (0 = rok 1900)
    int   tm_wday;    // Dzień tygodnia: 0 ÷ 6
    int   tm_yday;    // Dzień roku: 0 ÷ 365
    int   tm_isdst;   // Wartość > 0 – czas letni, 0 – zimowy
};

zwracając jej wskaźnik. Rozważania te prowadzą do następu­jącego programu:

#include <iostream>
#include <iomanip>
#include <ctime>
#include "bconio.h"
#include "kalend.h"
#include "klaw.h"

using namespace std;

const string Mc[] = {"",
    "Styczen", "Luty", "Marzec", "Kwiecien", "Maj", "Czerwiec", "Lipiec",
    "Sierpien", "Wrzesien", "Pazdziernik", "Listopad", "Grudzien"};

struct date
{
    int da_year;    // rok
    char da_day;    // dzień
    char da_mon;    // miesiąc
};

void getdate(date *p)
{
    time_t czas;
    time(&czas);
    tm *t = localtime(&czas);
    p->da_year = t->tm_year + 1900;
    p->da_mon = t->tm_mon + 1;
    p->da_day = t->tm_mday;
}

void miesiac(int m, int r)
{
    clrscr();
    cout << setw((17 - Mc[m].length()) / 2) << "" << Mc[m] << setw(5)
         << r << "\n --------------------\n Nd Po Wt Sr Cz Pt So\n";
    int n = dtyg(1, m, r), max = dmax(m, r);
    if (n > 0)
        cout << setw(3 * n) << "";
    for (int d = 1; d <= max; d++)
    {
        cout << setw(3) << d;
        if ((d + n) % 7 == 0)
            cout << endl;
    }
    gotoxy(3, 11);
    cout << "Menu: Up, Dn, Esc";
}

int main()
{
    date dt;
    getdate(&dt);
    miesiac(dt.da_mon, dt.da_year);
    int c;
    while ((c = czytajZnak()) != K_ESC)
    {
        if (c == K_DN)
        {
            if (dt.da_mon++ < 12)
                miesiac(dt.da_mon, dt.da_year);
            else
                miesiac(dt.da_mon = 1, ++dt.da_year);
        }
        else if (c == K_UP)
        {
            if (--dt.da_mon > 0)
                miesiac(dt.da_mon, dt.da_year);
            else
                miesiac(dt.da_mon = 12, --dt.da_year);
        }
    }
}

Program w Visual C++

W Visual C++ typ time_t i funkcja time odnoszą się domyślnie do wartości 64-bitowych. Można wymusić, aby były to wartości 32-bitowe. Nie jest to jednak zalecane, ponieważ wyko­nanie programu mogłoby zakończyć się niepowo­dzeniem po 18 sty­cznia 2038 roku (dla wartości 64-bitowych czas jest przedłu­żony do 31 gru­dnia 3000 roku). Konwersji wartości time_t na strukturę tm dokonuje dwuargu­mentowa funkcja localtime_s, której pierwszym argu­mentem jest wskaźnik na strukturę typu tm, drugi na zmienną typu time_t. Przez analogię z wersją programu dla MinGW C++, otrzymu­jemy nastę­pującą wersję tego programu dla kompila­tora Visual C++:

#include <iostream>
#include <iomanip>
#include <ctime>
#include <string>
#include "bconio.h"
#include "kalend.h"
#include "klaw.h"

using namespace std;

const string Mc[] = {"",
    "Stycze\xe4", "Luty", "Marzec", "Kwiecie\xe4", "Maj", "Czerwiec", "Lipiec",
    "Sierpie\xe4", "Wrzesie\xe4", "Pa\xab" "dziernik", "Listopad", "Grudzie\xe4"};

struct date
{
    int da_year;    // rok
    char da_day;    // dzień
    char da_mon;    // miesiąc
};

void getdate(date *p)
{
    time_t czas;
    time(&czas);
    tm t;
    localtime_s(&t, &czas);
    p->da_year = t.tm_year + 1900;
    p->da_mon = t.tm_mon + 1;
    p->da_day = t.tm_mday;
}

void miesiac(int m, int r)
{
    clrscr();
    cout << setw((17 - Mc[m].length()) / 2) << "" << Mc[m] << setw(5)
         << r << "\n --------------------\n Nd Po Wt \x97r Cz Pt So\n";
    int n = dtyg(1, m, r), max = dmax(m, r);
    if (n > 0)
        cout << setw(3 * n) << "";
    for (int d = 1; d <= max; d++)
    {
        cout << setw(3) << d;
        if ((d + n) % 7 == 0)
            cout << endl;
    }
    gotoxy(3, 11);
    cout << "Menu: Up, Dn, Esc";
}

int main()
{
    date dt;
    getdate(&dt);
    miesiac(dt.da_mon, dt.da_year);
    int c;
    while ((c = czytajZnak()) != K_ESC)
    {
        if (c == K_DN)
        {
            if (dt.da_mon++ < 12)
                miesiac(dt.da_mon, dt.da_year);
            else
                miesiac(dt.da_mon = 1, ++dt.da_year);
        }
        else if (c == K_UP)
        {
            if (--dt.da_mon > 0)
                miesiac(dt.da_mon, dt.da_year);
            else
                miesiac(dt.da_mon = 12, --dt.da_year);
        }
    }
}

W programie uwzględniono polskie znaki diakrytyczne kodowane w oknie konsoli według strony kodowej CP852. Na przykład litera Ś ma kod szesna­stkowy 0x97 (dzie­siętny 151), więc w językach C i C++ można ją zapisać jako znak '\x97', a łańcuch określa­jący dzień tygodnia jako "\x97roda". Po sekwencji \x powinna wystąpić co najmniej jedna cyfra szesna­stkowa (cyfra dziesiętna od 0 do 9 bądź mała lub duża litera od a do f). Kompi­lator może mieć problem z interpre­tacją takiego ciągu. Na przykład w przypadku łańcucha

"Pa\xabdziernik"

w którym po dwuznaku \x występuje ciąg trzech cyfr szesna­stkowych, podejmuje nieudaną próbę potrakto­wania go jako znak o kodzie 0xabd (dziesię­tnie wartość 2749 za duża na znak). Błąd łatwo naprawić, dzieląc łańcuch na dwa podłańcuchy:

"Pa\xab" "dziernik"

Efekt tych zabiegów ilustruje poniższy rysunek.


Opracowanie przykładu: wrzesień 2018