Serwis Edukacyjny
w I-LO w Tarnowie
obrazek

Materiały dla uczniów liceum

  Wyjście       Spis treści       Wstecz       Dalej  

Autor artykułu: mgr Jerzy Wałaszek
Zmodyfikowano 11.06.2023

©2023 mgr Jerzy Wałaszek
I LO w Tarnowie

Materiały do matury z informatyki

Tekst

SPIS TREŚCI

Tablica znaków

Przypomnijmy: typ znakowy char oznacza 8-bitowy znak ASCII. Kody podstawowe znaków mają zakres od 0 do 127. Kody rozszerzone mają kody od 128 do 255 (unsigned char) lub od -128 do -1 (signed char). Typ char tworzy zmienną, która potrafi przechować pojedynczy znak ASCII.

Tekst jest ciągiem znaków ASCII, zatem do jego przechowania potrzebna jest tablica znakowa, której poszczególne komórki przechowują kolejne znaki tekstu. Tablicę znakową tworzymy wg tych samych zasad, co tablice numeryczne:

char nazwa_tablicy[liczba_znaków];
lub
unsigned char nazwa_tablicy[liczba_znaków];
signed char nazwa_tablicy[liczba_znaków];

Tutaj pojawia się pewien problem. Teksty mogą mieć różną długość, czyli liczbę znaków. Aby efektywnie przetwarzać teksty, komputer musi wiedzieć, ile znaków dany tekst zawiera. W języku C++ przyjęto zasadę, iż tekst kończy się znakiem NUL, czyli kodem 0. Sam kod 0 nie należy do tekstu. Poniższy program odczytuje tekst, umieszcza go w tablicy znakowej, po czym wyświetla w oknie konsoli:

// Tekst
//------

#include <iostream>

using namespace std;


int main()
{
    char imie[100];

    setlocale(LC_ALL,"");

    cout << "Jak brzmi twoje imię? : ";
    cin >> imie;

    cout << endl << "Witaj " << imie << "!"
         << endl << endl;

    return 0;
}
Jak brzmi twoje imię? : Janusz

Witaj Janusz!

Przeanalizujmy działanie tego programu.

char imie[100];   Tworzymy tablicę znakową o pojemności 100 znaków. Tutaj chodzi o zapas, aby nie okazało się, iż wprowadzane imię nie zmieści się w tablicy.
setlocale(LC_ALL,"");   Ustawiamy w konsoli wyświetlanie polskich znaków Windows 1250.
cin >> imie;   Odczytujemy imię użytkownika do tablicy znakowej. Strumień wejścia automatycznie dodaje znak NUL za ostatnim znakiem tekstu. Zwróć uwagę, iż nazwa tablicy jest jednocześnie wskaźnikiem pierwszego jej znaku.
cout << endl << "Witaj " << imie << "!"
     << endl << endl;
  Wyświetlamy odczytane znaki. Zwróć uwagę, iż tutaj również nazwa tablicy jest potraktowana jako wskaźnik jej pierwszego znaku.

Wszystko jest wspaniale, dopóki wprowadzone imię nie zawiera polskich znaków:

Jak brzmi twoje imię? : Świętosława

Witaj -wictos?awa!

Co się stało? Otóż okazuje się, iż funkcja setlocale( ) zmieniła jedynie sposób wyświetlania polskich znaków. Odczyt dalej jest w LATIN 2! To zalety wspaniałego systemu Windows. W Linxie wszystko jest w porządku, a funkcja setlocale( ) wcale nie jest potrzebna. Rozwiązaniem problemu jest stworzenie prostej funkcji, która przetworzy odczytany tekst na kodowanie Win 1250:

// Tekst
//------

#include <iostream>

using namespace std;

void PL(char * t)
{
    while(*t)
    {
        switch((unsigned char)*t)
        {
            case 164 : *t = 165; break;
            case 143 : *t = 198; break;
            case 168 : *t = 202; break;
            case 157 : *t = 163; break;
            case 227 : *t = 209; break;
            case 224 : *t = 211; break;
            case 151 : *t = 140; break;
            case 141 : *t = 143; break;
            case 189 : *t = 175; break;
            case 165 : *t = 185; break;
            case 134 : *t = 230; break;
            case 169 : *t = 234; break;
            case 136 : *t = 179; break;
            case 228 : *t = 241; break;
            case 162 : *t = 243; break;
            case 152 : *t = 156; break;
            case 171 : *t = 159; break;
            case 190 : *t = 191; break;
            default  : break;
        }
        t++;
    }
}

//---------------------------------------

int main()
{
    char imie[100];

    setlocale(LC_ALL,"");

    cout << "Jak brzmi twoje imię? : ";
    cin >> imie; PL(imie);

    cout << endl << "Witaj " << imie << "!"
         << endl << endl;

    return 0;
}
Jak brzmi twoje imię? : książę_zażółć_gęślą_jaźń

Witaj książę_zażółć_gęślą_jaźń!

Jak działa nasza funkcja:

void PL(char * t)   Funkcja w parametrze t otrzymuje adres tablicy znakowej, której znaki są w kodzie LATIN2.
 while(*t)   Tworzymy pętlę, w której wskaźnik t przechodzi przez kolejne znaki tekstu, aż do napotkania kodu NUL. Wtedy wyrażenie *t ma wartość 0, czyli logicznie jest fałszywe i pętla while zakończy działanie.
switch((unsigned char)*t)   W pętli sprawdzamy kody napotkanych znaków traktowane jako liczby bez znaku (inaczej kody byłyby ujemne).
kod_LATIN2: *t = kod_WIN_1250; break;   Jeśli napotkany znak ma kod LATIN2 polskiego znaku, to zmieniamy go w tablicy na kod WIN 1250. Rozkaz break; kończy działanie instrukcji switch( ). Znaki nie będące kodami LATIN2 polskich znaków nie są zmieniane (klauzula default:). Znaki są traktowane w języku C++ jak liczby o wartości kodów tych znaków.
t++;   Po wyjściu z instrukcji switch( ) wskaźnik t jest zwiększany i wskazuje kolejny znak w tablicy. Wykonywane jest to na końcu każdego obiegu pętli while.

Zachowaj sobie tę funkcję. Jeszcze będzie przydatna.

Strumień cin pozwala odczytywać pojedyncze wyrazy, ponieważ spacja jest separatorem. Zatem jeśli wprowadzimy imię dwuwyrazowe, np. Don Kijote, to ze strumienia zostanie pobrany tylko pierwszy wyraz:

Jak brzmi twoje imię? : Don Kijote
Witaj Don!

Drugi wyraz pozostanie w strumieniu i wymaga ponownego użycia cin, aby go odczytać. Jeśli zatem chcemy odczytać cały wiersz, to musimy użyć funkcji składowej strumienia cin o nazwie getline( ). Funkcja ma dwa parametry: tablicę znakową do której zostanie wprowadzony tekst oraz maksymalną liczbę znaków w tablicy. Jeśli wprowadzony tekst jest dłuższy, to zostanie obcięty tak, aby zmieścił się w tablicy. Pozostałe znaki są usuwane ze strumienia. Ponieważ na końcu tekstu jest automatycznie dopisywany znak NUL, to do tablicy trafi tekst o liczbie znaków o jeden mniejszej od maksymalnej. Chodzi o to, aby obszar tablicy w pamięci nie został przekroczony, gdyż to mogłoby spowodować błędne działanie programu. Składnia funkcji jest następująca:

cin.getline(tablica_znakowa,maksymalna_liczba_znaków)

Sprawdź poniższy program:

// Tekst
//------

#include <iostream>

using namespace std;

void PL(char * t)
{
    while(*t)
    {
        switch((unsigned char)*t)
        {
            case 164 : *t = 165; break;
            case 143 : *t = 198; break;
            case 168 : *t = 202; break;
            case 157 : *t = 163; break;
            case 227 : *t = 209; break;
            case 224 : *t = 211; break;
            case 151 : *t = 140; break;
            case 141 : *t = 143; break;
            case 189 : *t = 175; break;
            case 165 : *t = 185; break;
            case 134 : *t = 230; break;
            case 169 : *t = 234; break;
            case 136 : *t = 179; break;
            case 228 : *t = 241; break;
            case 162 : *t = 243; break;
            case 152 : *t = 156; break;
            case 171 : *t = 159; break;
            case 190 : *t = 191; break;
            default  : break;
        }
        t++;
    }
}

int main()
{
    char imie[100];

    setlocale(LC_ALL,"");

    cout << "Jak brzmi twoje imię? : ";
    cin.getline(imie,100);
    PL(imie);

    cout << endl << "Witaj " << imie << "!"
         << endl << endl;

    return 0;
}
Jak brzmi twoje imię? : Król Maciuś Pierwszy Długouchy lecz Wąsaty

Witaj Król Maciuś Pierwszy Długouchy lecz Wąsaty!

Do zapamiętania:

Na początek:  podrozdziału   strony 

Proste funkcje dla tablic znakowych

Tablice znakowe pozwalają przechowywać tekst, lecz nie są wygodnym rozwiązaniem. Dlatego język C++ posiada specjalne obiekty do obsługi tekstów, zwane łańcuchami tekstowymi (ang. text strings). Zacznijmy jednak od początku. Zawartość tablicy znakowej można określić w jej definicji:

char tablica_znakowa[ ] = "...dowolny tekst...";

Zwróć uwagę, iż w tym przypadku nie musimy podawać rozmiaru tablicy, kompilator wyliczy rozmiar z długości tekstu, chociaż możesz to zrobić np. w sytuacji, gdy chcesz mieć tablicę o większej liczbie znaków, niż wynika to z wprowadzanego do niej tekstu. Uruchom poniższy program:

// Tekst
//------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    char a[] = "Miś Uszatek";

    cout << a << endl << endl;

    return 0;
}
Miś Uszatek

Dostęp do poszczególnych znaków tekstu uzyskujemy standardowo poprzez indeksy. Pierwszy znak tekstu ma indeks 0. Uruchom zmodyfikowany program:

// Tekst
//------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    char a[] = "Miś Uszatek";

    cout << a << endl;

    a[0] = 'L';
    a[2] = 's';

    cout << a << endl << endl;

    return 0;
}
Miś Uszatek
Lis Uszatek

Zwróć uwagę, iż do komórek tablicy zapisujemy kody znaków. Do tego celu w języku C++ używane są apostrofy. 'L' oznacza kod litery L, czyli 76. Zapis "L" oznacza tekst, czyli literkę L oraz znak NUL, który jest wstawiany na końcu tekstu i w rzeczywistości jest adresem do miejsca w pamięci, w którym kompilator umieścił te litery. Dlatego przypisanie a[0] = "L"; nie ma sensu, ponieważ komórka a[0] może przechować tylko kod znaku, a nie adres tekstu w pamięci. W trakcie przypisania w definicji tablicy:

char a[] = "Miś Uszatek";

komputer wykorzystuje adres tekstu "Miś Uszatek" do skopiowania poszczególnych znaków to komórek tablicy a, gdy ją tworzy. Mógłbyś wpisywać poszczególne kody znaków do tablicy:

char a[] = {'M','i','ś',' ','U','s','z','a','t','e','k','\0'};

lub w postaci liczbowej :

char a[] = {77,105,(signed char)156,32,85,115,122,97,116,101,107,0};

Przyznasz zatem, że pierwszy sposób jest najprostszy. Na końcu zawsze należy umieścić znak NUL, inaczej tekst ucieknie poza tablicę.

Gdy tablica zostanie zdefiniowana, przypisanie tekstu już nie działa:

// Tekst
//------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    char a[100];

    a = "Miś Uszatek";

    cout << a << endl << endl;

    return 0;
}

Zatem, aby wprowadzić tekst do tablicy należy wykorzystać funkcję (najlepiej biblioteczną) lub wprowadzać tekst litera po literze do poszczególnych komórek. Funkcje operujące na tablicach znakowych zdefiniowane są w pliku nagłówkowym cstring, który należy dołączyć do programu dyrektywą #include <cstring>. Poznamy teraz kilka z tych funkcji. Wszystkich funkcji jest dużo więcej, możesz je bez problemu znaleźć w Internecie szukając wg haseł c++ cstring.

strlen( )

Nazwa funkcji pochodzi od słów angielskich string length = długość tekstu. Funkcja jako parametr przyjmuje adres tekstu zakończonego znakiem NUL. Jako wynik funkcja zwraca liczbę znaków od początku tekstu do znaku poprzedzającego znak NUL. Składnia funkcji jest następująca:

int strlen(tekst);

Uruchom poniższy program:

// Funkcje tekstowe
//-----------------

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    cout << "Liczba znaków: "
         << strlen("Miś Uszatek")
         << endl << endl;
    return 0;
Liczba znaków: 11

Tekst w apostrofach jest w C++ adresem w pamięci pierwszego znaku tekstu. Dlatego możemy go przekazać w parametrze do funkcji strlen( ).

 Kolejny program wykorzystuje tablicę znakową:

// Funkcje tekstowe
//-----------------

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    char a[] = "Miś uszatek i lisek Chytrusek";
    cout << "Liczba znaków w \"" << a << "\" = "
         << strlen(a)
         << endl << endl;
    return 0;
}
Liczba znaków w "Miś uszatek i lisek Chytrusek" = 29

Tutaj tworzymy tablicę znakową a i wprowadzamy do niej tekst. Następnie wypisujemy tekst oraz jego długość. Jeśli chcemy wyświetlić cudzysłów, to poprzedzamy go znakiem \. Inaczej cudzysłów zostanie potraktowany jako znak końca tekstu, czyli tzw. delimiter. Nazwa tablicy jest adresem jej pierwszego znaku. Zatem jako parametr strlen( ) możemy przekazać nazwę tablicy znakowej.

Trzeci program odczytuje dowolny tekst z okna konsoli. Polskie znaki są zamieniane z LATIN2 na kodowanie WIN1 1250, aby były poprawnie wyświetlone w oknie konsoli. Wykorzystujemy tu funkcję PL( ), którą utworzyliśmy w poprzednim podrozdziale.

// Funkcje tekstowe
//-----------------

#include <iostream>
#include <cstring>

using namespace std;

void PL(char * t)
{
    while(*t)
    {
        switch((unsigned char)*t)
        {
            case 164 : *t = 165; break;
            case 143 : *t = 198; break;
            case 168 : *t = 202; break;
            case 157 : *t = 163; break;
            case 227 : *t = 209; break;
            case 224 : *t = 211; break;
            case 151 : *t = 140; break;
            case 141 : *t = 143; break;
            case 189 : *t = 175; break;
            case 165 : *t = 185; break;
            case 134 : *t = 230; break;
            case 169 : *t = 234; break;
            case 136 : *t = 179; break;
            case 228 : *t = 241; break;
            case 162 : *t = 243; break;
            case 152 : *t = 156; break;
            case 171 : *t = 159; break;
            case 190 : *t = 191; break;
            default  : break;
        }
        t++;
    }
}

int main()
{
    setlocale(LC_ALL,"");

    char a[256];
    cout << "Wpisz swój tekst: ";
    cin.getline(a,256);
    PL(a);
    cout << "Liczba znaków tekstu \"" << a << "\" = "
         << strlen(a)
         << endl << endl;
    return 0;
}
Wpisz swój tekst: Aligator Józek zjadł swój wózek
Liczba znaków tekstu "Aligator Józek zjadł swój wózek" = 31

strcpy( )

Nazwa funkcji pochodzi od słów angielskich string copy = kopiowanie tekstu. Składnia funkcji jest następująca:

char * strcpy(tekst_docelowy,tekst_źródłowy)

Jako argumenty funkcja wymaga dwóch adresów (wskaźników): tekst_docelowy jest adresem tablicy znakowej, do której trafi tekst spod adresu tekst_źródłowy. Tekst źródłowy jest kopiowany znak po znaku do momentu, aż funkcja skopiuje znak NUL. Wtedy kopiowanie się kończy. Należy zadbać, aby tablica docelowa miała wystarczającą pojemność na tekst źródłowy. Dzięki tej funkcji możemy wprowadzać do tablicy znakowej tekst, czyli jest ona ograniczonym odpowiednikiem operacji przypisania. Wynikiem funkcji jest adres tekstu docelowego. Tekst docelowy i tekst źródłowy nie powinny się pokrywać w pamięci. Uruchom poniższy program:

int main()
{
    setlocale(LC_ALL,"");

    char a[256];
    strcpy(a,"Baba Jaga");
    cout << a << endl;
    strcpy(a,"Nietoperz Gacuś");
    cout << a << endl;
    strcpy(a,"Kotek Młotek");
    cout << a << endl << endl;
    return 0;
}
Baba Jaga
Nietoperz Gacuś
Kotek Młotek

strcat( )

Nazwa funkcji pochodzi od angielskich słów string catenation = połączenie tekstów. Funkcja wymaga dwóch argumentów, które są wskaźnikami:

char * strcat(tekst_docelowy,tekst_źródłowy);

Tekst źródłowy zostaje dołączony na końcu tekstu docelowego w ten sposób, iż kończący znak NUL tekstu docelowego jest nadpisywany pierwszym znakiem tekstu źródłowego. Kolejne znaki tekstu źródłowego są następnie kopiowane do tekstu docelowego aż do skopiowania znaku NUL. Funkcja zwraca w wyniku wskaźnik do tekstu docelowego. Tablica docelowa powinna posiadać wystarczającą pojemność, aby przechować oba teksty. Uruchom program:

// Funkcje tekstowe
//-----------------

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    char a[256];

    cout << strcpy(a,"Miś Uszatek") << endl;
    cout << strcat(a," i klapnięte uszko ma")
         << endl << endl;
    return 0;
}
Miś Uszatek
Miś Uszatek i klapnięte uszko ma

Zwróć uwagę, iż w programie do strumienia cout przekazywane są wyniki funkcji strcpy( ) i strcat( ). W obu przypadkach jest to po prostu tablica znakowa a.


Zadania

Do zapamiętania:

Na początek:  podrozdziału   strony 

Klasa string

Tablice znakowe są reliktem poprzednika języka C++języka C. Pliki nagłówkowe funkcji z języka C posiadają na początku swojej nazwy literę c: cstring – funkcje tekstowe z języka C. Wciąż można ich używać, dzięki czemu programiści języka C mogą przesiąść się bezproblemowo na język C++. Jednak język C++ oferuje lepsze metody obsługi tekstów. Jedną z nich jest klasa string. Klasa (ang. class) w języku C++ jest obiektem, który zawiera typ danych o nazwie string oraz zbór funkcji składowych, które obsługują te dane w klasie. Klasy są bardzo wygodnym rozwiązaniem, co zobaczymy dalej. Typ string oznacza coś zbliżonego do tablicy znakowej, jednakże jest to tablica dynamiczna, która automatycznie dostosowuje swój rozmiar do zawartych w niej danych. Dzięki temu nie musimy się martwić, iż wprowadzane dane wyjdą poza tablicę i nadpiszą nam inne zmienne w programie.

Aby skorzystać w swoim programie z klasy string należy najpierw dołączyć plik nagłówkowy o nazwie string za pomocą dyrektywy #include <string>. Od tego momentu możemy korzystać z klasy string oraz z funkcji składowych tej klasy. Najpierw zobaczmy prosty przykład:

// Klasa string
//-------------

#include <iostream>
#include <string>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    string a;

    a = "Miś Uszatek";
    a += " lubi miodek!";
    a += "\n" + a;

    cout << a << endl << endl;

    return 0;
}
Miś Uszatek lubi miodek!
Miś Uszatek lubi miodek!

Jak widzisz, zmienna a klasy string zachowuje się jak normalna zmienna. Działa dla niej operator przypisania oraz niektóre operatory modyfikacji. Przeanalizujmy ważniejsze elementy w programie:

#include <string>   dołączamy plik nagłówkowy z definicją klasy string. Od tego momentu otrzymujemy nowy typ string i możemy tworzyć zmienne klasy string.
string a;   Tworzymy zmienną a klasy string. Nowo utworzona zmienna zawiera tekst pusty.
a = "...text1...";   Instrukcja przypisania umieszcza w zmiennej a tekst1.
a += "...text2...";   Instrukcja modyfikacji. Operator dodawania + oznacza dla tekstów łączenie, zatem tekst2 zostanie dopisany na koniec tekstu, który już znajduje się w zmiennej a. W rezultacie w zmiennej a otrzymamy połączone ze sobą tekst1 i tekst2.
a += "\n" + a;   Na koniec połączonych tekstów w zmiennej a zostaje wstawiony znak końca wiersza /n oraz poprzednia zawartość zmiennej a. W efekcie tekst w a zostanie zdublowany.
cout << a << endl << endl;   Przesłanie zmiennej a do strumienia cout spowoduje wyświetlenie w oknie konsoli jej zawartości. Będą to dwa wiersze, ponieważ pomiędzy nimi został wstawiony znak końca wiersza \n.

Już ten prosty przykład pokazuje, iż zmienna typu string jest prostsza w obsłudze od tablicy znakowej char, dlatego w dalszej części tego kursu będziemy używali wyłącznie klasy string do tekstów.

Zmienna string może być zainicjowana określoną treścią już w momencie tworzenia:

string zmienna("...tekst...");

// Klasa string
//-------------

#include <iostream>
#include <string>

using namespace std;


int main()
{
    setlocale(LC_ALL,"");

    string a("Cześć, tu string :)");

    cout << a << endl << endl;

    return 0;
}
Cześć, tu string :)

Wygląda to jak wywołanie funkcji i faktycznie nim jest. Gdy zmienna klasy jest tworzona w pamięci komputera, zostaje wywołana specjalna funkcja składowa tej klasy zwana konstruktorem. Zadaniem konstruktora jest odpowiednie przygotowanie utworzonego obiektu do pracy w programie. Standardowy konstruktor ustawia długość tekstu w zmiennej string na 0, czyli na tekst pusty, który nie zawiera żadnego znaku. Zmieniamy to podając w parametrze dla konstruktora tekst, który chcemy, aby znalazł się w zmiennej od początku jej istnienia. Wszystko to omówimy dokładnie przy klasach C++ w dalszej części kursu.

Można też użyć operatora przypisania, jak w normalnych zmiennych:

string a = "Cześć, tu string :)";

Dostęp do poszczególnych znaków w zmiennej string uzyskuje się przy pomocy indeksów tak samo jak dla tablicy.

Liczbę znaków (a właściwie liczbę bajtów, jednak dla kodu ASCII jest to to samo) zwraca funkcja składowa lenght( ). Funkcję składową klasy zawsze wywołujemy z nazwą zmiennej oddzieloną operatorem kropka:

zmienna_string.length();

Kolejny program odczytuje jeden wyraz ze strumienia cin i umieszcza go w zmiennej typu string. Następnie program wyświetla liczbę znaków w wyrazie oraz kolejne znaki wyrazu i ich kody. W programie umieściliśmy zmodyfikowaną dla klasy string funkcję PL( ), która zamienia znaki w kodzie LATIN2 na kod WIN 1250.

// Klasa string
//-------------

#include <iostream>
#include <string>

using namespace std;

void PL(string & t)
{
    unsigned i,len = t.length();
    for(i = 0; i < len; i++)
        switch((unsigned char) t[i])
        {
            case 164 : t[i] = 165; break;
            case 143 : t[i] = 198; break;
            case 168 : t[i] = 202; break;
            case 157 : t[i] = 163; break;
            case 227 : t[i] = 209; break;
            case 224 : t[i] = 211; break;
            case 151 : t[i] = 140; break;
            case 141 : t[i] = 143; break;
            case 189 : t[i] = 175; break;
            case 165 : t[i] = 185; break;
            case 134 : t[i] = 230; break;
            case 169 : t[i] = 234; break;
            case 136 : t[i] = 179; break;
            case 228 : t[i] = 241; break;
            case 162 : t[i] = 243; break;
            case 152 : t[i] = 156; break;
            case 171 : t[i] = 159; break;
            case 190 : t[i] = 191; break;
            default  : break;
        }
}

int main()
{
    setlocale(LC_ALL,"");

    string a;

    cout << "Wpisz pojedynczy wyraz: ";

    cin >> a;

    PL(a); // Korekcja odczytanych kodów

    cout << endl << "Liczba znaków : " << a.length()
         << endl << endl;

    for(unsigned i = 0; i < a.length(); i++)
        cout << "Znak: " << a[i]
             << " kod: " << (int)(unsigned char)a[i]
             << endl;
    cout << endl;

    return 0;
}
Wpisz pojedynczy wyraz: książę

Liczba znaków : 6

Znak: k kod: 107
Znak: s kod: 115
Znak: i kod: 105
Znak: ą kod: 185
Znak: ż kod: 191
Znak: ę kod: 234

Przeanalizujmy ważniejsze elementy tego programu:

string a;   Tworzymy zmienną a klasy string. Zmienna a nie zawiera żadnego tekstu.
cin >> a;   Do zmiennej a odczytujemy jeden wyraz znaków ze strumienia cin. Jeśli wprowadzisz więcej niż jeden wyraz, ze strumienia cin zostanie odczytany tylko pierwszy z nich. Spacja jest traktowana jako koniec wyrazu. Spacje początkowe są ignorowane.
cout ... << a.length()   Do strumienia wyjściowego cout przekazujemy liczbę znaków w zmiennej a, czyli w odczytanym wyrazie.
for(unsigned i=0;i<a.length();i++)   Pętla for wykona się tyle razy, ile wynosi liczba znaków w a. Zmienna i przebiegnie indeksy wszystkich znaków w a. Jeśli chcesz przyspieszyć działanie tej pętli, to liczbę znaków zapamiętaj w osobnej zmiennej i ją użyj w porównaniu. Dzięki temu komputer nie będzie musiał liczyć znaków w a w każdym obiegu pętli. Tak zrobiliśmy w funkcji PL( ):
unsigned i, len = a.length();
for(i = 0; i < len; i++)...
cout ... << a[i] ...   Do strumienia cout trafia znak ze zmiennej a, który znajduje się na pozycji i-tej.
cout ... << (int)(unsigned char)a[i] ...   Ta dziwna konstrukcja ma za zadanie przesłanie do strumienia cout kodu znaku jako liczby nieujemnej 8-bitowej. Dokonujemy tego dwoma rzutowaniami, które należy czytać od strony prawej do lewej. Najpierw znak a[i] zostaje zamieniony na znak typu unsigned char, a następnie ten znak zmieniany jest w typ int. W efekcie dla kodów rozszerzonych otrzymujemy liczbę dodatnią z zakresu od 128 do 255. Bez rzutowania kody rozszerzone ASCII byłyby ujemne, ponieważ prosty typ char jest 8-bitowym odpowiednikiem typu int i jest interpretowany jako 8-bitowa liczba U2.

Jeśli chcemy odczytać cały wiersz ze strumienia, używamy funkcji getline( ), jednak w tym przypadku funkcja ta należy do klasy string i nie jest funkcją składową strumienia cin. Jest to inna funkcja, posiada ona jedynie taką samą nazwę:

Odczyt wiersza do tablicy char:

cin.getline(tablica_char,liczba_znaków);

Odczyt wiersza do zmiennej klasy string:

getline(cin,zmienna_string,{delimiter})

To częsta praktyka w języku C++. Mogą istnieć różne funkcje o tej samej nazwie tak, jak mogą istnieć różni uczniowie o tym samym nazwisku lub imieniu. Funkcja getline( ) dla klasy string wymaga dwóch lub trzech argumentów:
cin   Strumień wejścia, z którego będą odczytywane znaki tworzące wiersz. W tym miejscu może pojawić się dowolny inny strumień wejścia, niekoniecznie musi to być cin – w ten sposób możemy odczytywać wiersz z różnych urządzeń, np. z pliku na dysku czy z pamięci komputera, co zobaczymy później.
zmienna_string   Zmienna klasy string, w której zostanie umieszczony wiersz znaków. Nie ma tutaj ograniczenia na długość wiersza, gdyż zmienne string dynamicznie dopasowują się do ilości danych.
{delimiter}   Ten parametr nie jest obowiązkowy. Możesz go nie podawać w wywołaniu funkcji. Jeśli go pominiesz, to wiersz zostanie wczytany do momentu napotkania w strumieniu znaku końca wiersza \n. W przeciwnym razie odczyt będzie następował aż do napotkania znaku delimitera. W obu przypadkach znak kończący wiersz (\n lub delimiter) jest usuwany ze strumienia, lecz nie jest wstawiany do zmiennej string.

Uruchom poniższy program. Odczytuje on wiersz znaków, po czym wypisuje go wspak.

// Klasa string
//-------------

#include <iostream>
#include <string>

using namespace std;

void PL(string & t)
{
    unsigned i,len = t.length();
    for(i = 0; i < len; i++)
        switch((unsigned char) t[i])
        {
            case 164 : t[i] = 165; break;
            case 143 : t[i] = 198; break;
            case 168 : t[i] = 202; break;
            case 157 : t[i] = 163; break;
            case 227 : t[i] = 209; break;
            case 224 : t[i] = 211; break;
            case 151 : t[i] = 140; break;
            case 141 : t[i] = 143; break;
            case 189 : t[i] = 175; break;
            case 165 : t[i] = 185; break;
            case 134 : t[i] = 230; break;
            case 169 : t[i] = 234; break;
            case 136 : t[i] = 179; break;
            case 228 : t[i] = 241; break;
            case 162 : t[i] = 243; break;
            case 152 : t[i] = 156; break;
            case 171 : t[i] = 159; break;
            case 190 : t[i] = 191; break;
            default  : break;
        }
}

int main()
{
    setlocale(LC_ALL,"");

    string a;

    cout << "Wpisz poniżej wiersz znaków:" << endl;

    getline(cin,a); PL(a);

    for(unsigned i = a.length(); i > 0; i--)
        cout << a[i - 1];
    cout << endl << endl;

    return 0;
}
Wpisz poniżej wiersz znaków:
Zażółć żabią jaźń, książę!
!ężąisk ,ńźaj ąibaż ćłóżaZ

Zadania

Do zapamiętania:

Na początek:  podrozdziału   strony 

Funkcje składowe klasy string

Zaletą klasy string jest jej integracja ze środowiskiem języka C++. Język C++ jest językiem obiektowym, a jego obiekty potrafią ze sobą współpracować. Funkcja składowa (ang. member function) jest funkcją, która została skojarzona z obiektem, jest jakby jego częścią składową i posiada bezpośredni dostęp do elementów obiektu oraz do innych jego funkcji składowych. Jeśli utworzysz kilka zmiennych klasy string, to każda z nich posiada swoje własne funkcje składowe o tych samych nazwach. Dostęp do nich uzyskuje się za pomocą operatora kropki . (ang. dot operator – member selection operator), który służy do wyboru elementu składowego obiektu. Składnia jest następująca:

obiekt.element_składowy;
obiekt.funkcja_składowa(argumenty);

Omówimy teraz kilka przydatnych funkcji składowych zmiennych klasy string.

length( )

Tę funkcję już poznaliśmy. Zwraca ona liczbę bajtów zawartych w zmiennej klasy string. Dla kodu ASCII będzie to to samo, co liczba znaków (możliwe jest również inne kodowanie, ale tym sobie nie zawracajmy na początku głowy). Funkcja length( ) przydaje się, gdy chcemy poznać długość tekstu przechowywanego w zmiennej typu string. Zmienne klasy string nie mają ograniczeń na długość tekstu.

Uruchom program:

// Funkcje składowe string
//------------------------

#include <iostream>
#include <string>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    string a = "ABC:";

    do
    {
        cout << "Długość  : " << a.length() << endl
             << "Tekst w a: " << a << endl << endl;
        a += "X";
    } while(a.length() <= 10);

    return 0;
}
Długość  : 4
Tekst w a: ABC:

Długość  : 5
Tekst w a: ABC:X

Długość  : 6
Tekst w a: ABC:XX

Długość  : 7
Tekst w a: ABC:XXX

Długość  : 8
Tekst w a: ABC:XXXX

Długość  : 9
Tekst w a: ABC:XXXXX

Długość  : 10
Tekst w a: ABC:XXXXXX

resize( )

Funkcja zmienia rozmiar tekstu w zmiennej string. Posiada jeden lub dwa parametry:

zmienna_string.resize(rozmiar);
zmienna_string.resize(rozmiar,znak);

Pierwsza postać używana jest najczęściej do zmniejszania długości tekstu. Rozmiar określa nową długość tekstu w zmiennej. Jeśli rozmiar jest mniejszy od aktualnej długości tekstu w zmiennej, to tekst zostanie obcięty do rozmiaru. Jeśli rozmiar jest większy od długości tekstu, to tekst zostanie powiększony do rozmiaru przez dodanie na końcu znaków NUL, które są traktowane w zmiennej string jako normalne znaki i nie oznaczają końca tekstu jak w tablicach char. Uruchom program:

// Funkcje składowe string
//------------------------

#include <iostream>
#include <string>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    string a = "Kubuś i Prosiaczek";

    cout << a << endl;

    a.resize(5);

    cout << a << "?" << endl << endl;

    return 0;
}
Kubuś i Prosiaczek
Kubuś?

Poeksperymentuj z tym programem zwiększając rozmiar.

Druga postać posiada dodatkowo parametr znak. Jeśli tekst ma więcej znaków niż rozmiar, to zostanie obcięty do rozmiaru. Jeśli tekst ma mniej znaków niż rozmiar, to zostanie uzupełniony podanym znakiem aż do osiągnięcia rozmiaru. Uruchom poniższy program:

// Funkcje składowe string
//------------------------

#include <iostream>
#include <string>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    string a = "Programowanie w języku C";

    cout << a << endl;

    a.resize(a.length() + 2,'+');

    cout << a << endl;

    a.resize(a.length() - 13,'-');

    cout << a << endl << endl;

    return 0;
}
Programowanie w języku C
Programowanie w języku C++
Programowanie

empty( )

Funkcja zwraca wartość logiczną true, jeśli tekst zawarty w zmiennej string jest pusty (tzn. nie zawiera żadnego znaku). W przeciwnym razie zwracane jest false.

// Funkcje składowe string
//------------------------

#include <iostream>
#include <string>

using namespace std;

void PL(string & t)
{
    unsigned i,len = t.length();
    for(i = 0; i < len; i++)
        switch((unsigned char) t[i])
        {
            case 164 : t[i] = 165; break;
            case 143 : t[i] = 198; break;
            case 168 : t[i] = 202; break;
            case 157 : t[i] = 163; break;
            case 227 : t[i] = 209; break;
            case 224 : t[i] = 211; break;
            case 151 : t[i] = 140; break;
            case 141 : t[i] = 143; break;
            case 189 : t[i] = 175; break;
            case 165 : t[i] = 185; break;
            case 134 : t[i] = 230; break;
            case 169 : t[i] = 234; break;
            case 136 : t[i] = 179; break;
            case 228 : t[i] = 241; break;
            case 162 : t[i] = 243; break;
            case 152 : t[i] = 156; break;
            case 171 : t[i] = 159; break;
            case 190 : t[i] = 191; break;
            default  : break;
        }
}

int main()
{
    setlocale(LC_ALL,"");

    string a;

    cout << "Wprowadź poniżej wiersz tekstu:" << endl;

    getline(cin,a); PL(a); cout << endl;

    while(!a.empty()) // Dopóki tekst NIE pusty
    {
        a.resize(a.length()-1);
        cout << a << "***" << endl;
    }
    cout << endl;

    return 0;
}
Wprowadź poniżej wiersz tekstu:
Zażółć żabią jaźń

Zażółć żabią jaź***
Zażółć żabią ja***
Zażółć żabią j***
Zażółć żabią ***
Zażółć żabią***
Zażółć żabi***
Zażółć żab***
Zażółć ża***
Zażółć ż***
Zażółć ***
Zażółć***
Zażół***
Zażó***
Zaż***
Za***
Z***
***

clear( )

Czyści zmienną string, tzn. usuwa cały tekst zawarty w zmiennej. Tę samą operację można wykonać przypisując zmiennej tekst pusty:

zmienna_string = "";

Uruchom program:

// Funkcje składowe string
//------------------------

#include <iostream>
#include <string>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    string a = "Krokodyl Arek";

    cout << "a = \"" << a << "\", liczba znaków = "
         << a.length() << endl;

    a.clear(); // Usuwamy tekst ze zmiennej

    cout << "a = \"" << a << "\", liczba znaków = "
         << a.length() << endl << endl;

    return 0;
}
a = "Krokodyl Arek", liczba znaków = 13
a = "", liczba znaków = 0

at( )

Umożliwia dostęp do znaku w zmiennej string na pozycji, którą podamy jako parametr. Funkcja ta dokładnie odpowiada indeksowaniu w klamerkach:

zmienna_string.at(pozycja) <---> zmienna_string[pozycja]

Dostęp do znaku oznacza, iż program może go odczytać, np:

cout << zmienna_string.at(pozycja) ...

lub dowolnie zmodyfikować, np:

zmienna_string.at(pozycja) = 'nowy znak';

Uruchom program:

// Funkcje składowe string
//------------------------

#include <iostream>
#include <string>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    string a = "Ala ma chomika";

    cout << a << endl;

    for(int i = a.length()-1; i >= 0; i--)
       cout << a.at(i);

    cout << endl << endl;

    return 0;
}
Ala ma chomika
akimohc am alA

Zmień w programie wywołanie a.at(i) na a[i].

back( )

Funkcja daje dostęp do ostatniego znaku tekstu przechowywanego w zmiennej string. zmienna_string.back() jest odpowiednikiem:

zmienna_string.at(zmienna_string.length()-1)
lub
zmienna_string[zmienna_string.length()-1]

Funkcji tej nie należy używać z tekstami pustymi.

// Funkcje składowe string
//------------------------

#include <iostream>
#include <string>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    string a = "Jaś";

    cout << a << endl;

    a.back() = 'n';

    cout << a << endl;

    return 0;
}
Jaś
Jan

front( )

Funkcja daje dostęp do pierwszego znaku tekstu w zmiennej string. Jest ona odpowiednikiem:

zmienna_string.at(0)
lub
zmienna_string[0]

Funkcji tej nie należy używać z tekstami pustymi.

// Funkcje składowe string
//------------------------

#include <iostream>
#include <string>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    string a = "jeść";

    cout << a << endl;

    a.front() = 't';

    cout << a << endl;

    return 0;
}
jeść
teść

push_back( )

Funkcja jako parametr przyjmuje znak, który zostanie dołączony na końcu bieżącego tekstu w zmiennej string. W efekcie długość tekstu wzrasta o 1.

zmienna_string.push_back(znak)

pop_back( )

Usuwa z tekstu w zmiennej string ostatni znak. W efekcie długość tekstu zmniejsza się o 1.

zmienna_string.pop_back()

// Funkcje składowe string
//------------------------

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string a;

    for(char i = 'a'; i <= 'z'; i++)
        a.push_back(i);

    do
    {
        cout << a << endl;
        a.pop_back();
    } while(!a.empty());

    return 0;
}
abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxy
abcdefghijklmnopqrstuvwx
abcdefghijklmnopqrstuvw
abcdefghijklmnopqrstuv
abcdefghijklmnopqrstu
abcdefghijklmnopqrst
abcdefghijklmnopqrs
abcdefghijklmnopqr
abcdefghijklmnopq
abcdefghijklmnop
abcdefghijklmno
abcdefghijklmn
abcdefghijklm
abcdefghijkl
abcdefghijk
abcdefghij
abcdefghi
abcdefgh
abcdefg
abcdef
abcde
abcd
abc
ab
a

Resztę funkcji składowych poznamy w miarę potrzeb.

Do zapamiętania:

Na początek:  podrozdziału   strony 

Zespół Przedmiotowy
Chemii-Fizyki-Informatyki

w I Liceum Ogólnokształcącym
im. Kazimierza Brodzińskiego
w Tarnowie
ul. Piłsudskiego 4
©2023 mgr Jerzy Wałaszek

Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone
pod warunkiem podania źródła oraz niepobierania za to pieniędzy.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.

Informacje dodatkowe.