Programowanie w Visual C++ 2010

Spis treści

Napisy (łańcuchy znaków)

Napisy (łańcuchy znaków) to tablice o elementach typu char (de facto to zwykłe, ,,małe'' liczby całkowite) zakończone bajtem równym zero (tzw. wartownikiem).

Tworzenie tablicy znaków

Dla pewności prześledźmy różne sposoby tworzenia tablicy zmiennych typu char:

 #include<iostream>
 using namespace std;

 int main()
 {
     char ulubiona_woda[9] = "Gazowana";
     char ulubiona_woda2[] = "Gazowana";

     char ulubione_ciastka[8] = {'M', 'a', 'r', 'k', 'i', 'z', 'y', '\0'};
     char ulubione_ciastka2[8] = {'M', 'a', 'r', 'k', 'i', 'z', 'y', NULL};

     char Twoje_zdanie[50];
     cout << "Co sadzisz o zeszlorocznym sniegu?" << endl;
     cin.getline(Twoje_zdanie, 50); // wczytaj cały wiersz z klawiatury

     cout << "Ulubiona woda: " << ulubiona_woda << "\t";
     cout << "2: "<< ulubiona_woda2 << endl;
     cout << "Ulubione ciastka: " << ulubione_ciastka << "\t";
     cout << "2: "<< ulubione_ciastka2 << endl;
     cout << "Twoje zdanie: " << Twoje_zdanie << endl;

     return 0;
}
 
Skopiuj powyższy kod do nowego projektu. Uruchom program.

Pierwsza i druga tablica będą identyczne - ulubiona_woda2 przyjmie rozmiar umieszczanego w niej napisu. Ale zaraz, zaraz... Przecież w słowie "Gazowana" mamy tylko 8 znaków! Czy nie powinniśmy zatem zmienić

     char ulubiona_woda[9] = "Gazowana";

na

     char ulubiona_woda[8] = "Gazowana"; // ?

Spróbujmy...

Spróbuj skompilować przykładowy program po dodaniu doń linijki
char ulubiona_woda[8] = "Gazowana";

Próby kompilacji przynoszą w tym wypadku negatywne rezultaty – kompilator wykrywa błędy:

Tworzenie tablicy char[]

Owe błędy dotyczą "przepełnienia" tablicy. Dlaczego tak się dzieje? Otóż inicjując ośmioelementową tablicę, możemy użyć tylko siedmioliterowego słowa. Wynika to z natury budowy łańcucha znaków – każdy napis musi być zakończony bajtem zerowym. Stąd łańcuch inicjowany słowem Gazowana dla komputera ma taką oto postać:

Budowa łańcucha znaków

Ostatni znak napisu (bajt równy zero) to tzw. wartownik. Kiedy kompilator napotka taką liczbę, będzie wiedział, że znajduje się na końcu odczytywanego ciągu. Jasne zatem są już konstrukcje z 9. i 10. linii powyższego listingu. Znaki '\0' i NULL oraz liczba całkowita 0 są równoważne.

Pamiętaj, aby rezerwować zawsze n+1 komórek pamięci dla napisów o długości n!
Najczęściej do reprezentacji napisów będziesz używać tablic alokowanych (za pomocą operatora new) dynamicznie. W tym samouczku stosujemy tablice o ustalonym rozmiarze tylko dla ilustracji omawianych funkcji!

Biblioteka <cstring>

Skoro wiemy już, jak tworzyć łańcuchy znaków, najwyższy czas się nimi nieco pobawić: trochę nimi pożonglujemy (czyt. pokopiujemy, posklejamy) i spróbujemy dobrać je w pary (czyli poporównujemy).

char tekst_baza[] = "Tak ma miła mnie mamiła ";
char tekst_dlugi[30];
char tekst_krotki[10];

strcpy(tekst_dlugi, tekst_baza);
strcpy(tekst_krotki, "krociutki");
strncpy(tekst_krotki, tekst_baza, 9);

O ile pierwsze trzy linie listingu są zrozumiałe, o tyle możesz mieć pewne problemy ze zrozumieniem linijek po nich następujących. Zapoznajmy się zatem z opisem poniższych funkcji:

strcpy(char* dest, const char* source)
kopiuje napis z tablicy source do tablicy dest. source nie musi być wcześniej zainicjowaną tablicą, może przyjmować postać napisu objętego cudzysłowami (np. "krociutki").
strncpy(char* dest, const char* source, size_t n)
kopiuje do tablicy dest pierwsze n znaków napisu source
Zauważ, że próba wykonania poniższego polecenia skutkuje wystąpieniem błędu. strcpy(tekst_krotki, tekst_baza); Pamiętaj, aby zawsze upewniać się, że długość napisu kopiowanego nie przekracza rozmiaru tablicy docelowej (również dla funkcji strncpy()). Taka pomyłka nie zostanie wykryta przez kompilator, może jednak doprowadzić do nagłego przerwania działania programu.
Być może kompilator ostrzega Cię przed używaniem funkcji strcpy(). Jeżeli nie chcesz, aby jego ostrzeżenia cieszyły Twoje oczy, możesz je zablokować. W tym celu:
  1. Wybierz Project|Project Properties.
  2. Kliknij Configuration Properties|C/C++|Preprocessor.
  3. Do Preprocessor Definitions dodaj _CRT_NO_SECURE_WARNINGS oddzielone średnikiem od pozostałej zawartości pola. Kliknij OK.

Kopiowanie mamy już przyswojone, rzućmy zatem okiem na konkatenację.

Konkatenacja jest operacją łączenia dwóch napisów w jeden. Można ją sobie wyobrażać jako doklejanie jednego napisu do drugiego.
char tekst1[50] = "Tracz tarl tarcice tak ";
char tekst2[] = "takt w takt, jak ";
char tekst3[] = " tartak tarcice tarl.";

strcat(tekst1, tekst2);
cout << "tekst: " << tekst1 <<e ndl;

char tekstcaly[100];
strcpy(tekstcaly, tekst1);

strncat(tekstcaly, tekst2, 11);
strncat(tekstcaly, tekst3, strlen(tekst3));

cout << "tekstcaly: " << tekstcaly << endl;

W powyższym przykładzie zastosowaliśmy trzy nowe funkcje:

strcat(char* dest, const char* source)
dołącza kopię napisu source na koniec dest
strncat(char* dest, const char* source, size_t n)
dołącza n pierwszych liter napisu source do dest lub - jeśli wśród tych znaków znajduje się znak pusty - do pierwszego napotkanego znaku pustego
strlen(const char* string)
zwraca długość napisu string (bez uwzględnienia kończącego napis znaku pustego). Zwróć uwagę, że długość napisu nie oznacza tego samego, co rozmiar tablicy. Owa funkcja zwraca liczbę komórek pamięci do napotkania pierwszego bajtu zerowego. Obliczona przez nią długość napisu może zatem być zarówno mniejsza, jak i większa (oznacza to, że coś jest nie tak) od rozmiaru tablicy.
Stosując funkcje konkatenujące, musisz pamiętać, by dołączany tekst nie spowodował przepełnienia tablicy docelowej.

Biblioteka <cctype>

Dzięki bibliotece <cstring> mogliśmy przeprowadzać różne operacje na łańcuchach znaków. <cctype> pozwala nam z kolei na manipulowanie elementami tychże łańcuchów czyli pojedynczymi znakami. Do dyspozycji mamy spory zestaw funkcji klasyfikujących znaki (nie będziemy omawiać tu wszystkich) oraz dwie funkcje pozwalające na ich konwersję.

Oto krótki przegląd elementów biblioteki <cctype>:

  1. Funkcje klasyfikujące
    isalnum(char c)
    Sprawdza, czy znak jest literą lub cyfrą dziesiętną.
    isalpha(char c)
    Sprawdza, czy znak jest wielką bądź małą literą alfabetu.
    isdigit(char c)
    Sprawdza, czy znak jest cyfrą dziesiętną.
    islower(char c)
    Sprawdza, czy znak jest małą literą alfabetu.
    isspace(char c)
    Sprawdza, czy znak jest tzw. "białym znakiem" (spacją, tabulatorem, nową linią itd.).
    isupper(char c)
    Sprawdza, czy znak jest wielką literą alfabetu.
  2. Funkcje modyfikujące
    tolower(char c)
    Konwertuje wielką literę alfabetu do jej małego odpowiednika. Jeśli nie istnieje taki odpowiednik, zwracana wartość to argument bez żadnych zmian.
    toupper(char c)
    Konwertuje małą literę alfabetu do jej wielkiego odpowiednika. Jeśli nie istnieje taki odpowiednik, zwracana wartość to argument bez żadnych zmian.

Wartości zwracane dla poszczególnych grup:

  1. liczba różna od 0 (równoważna wartości true), jeżeli znak spełnia warunek i 0 (czyli false) w przeciwnym wypadku;
  2. liczba całkowita odpowiadająca skonwertowanemu znakowi lub znakowi podanemu jako argument, jeśli konwersja nie istnieje.

W ramach przykładu: krótki programik zmieniający wielkie litery na małe i odwrotnie:

char napis[]= "KUUUkurYYYdzA GOOtoWaana! OOOrzeSZki w KARmeLU!";
for(int k=0; k<strlen(napis); k++)
{
   if (islower(napis[k]))
      napis[k] = toupper(napis[k]);
   else
      if (isupper(napis[k]))
         napis[k] = tolower(napis[k]);
}

cout << endl << napis << endl;

Przykład (*)

Zanim pożegnamy się z bibliotekami, spróbujmy stworzyć "słownik" wszystkich wyrazów występujących w pewnym napisie o identyfikatorze tekstcaly.


    cout << "SLOWNIK" << endl;
    char dictionary[15][10];
    char* word;

    word = strtok(tekstcaly, " ,.");
    int i = 0;
    bool exists = false;
    while (word!=NULL)
    {
       //sprawdzamy, czy w slowniku istnieje juz slowo 'word'
       for (int j=0; j<i; j++)
         if (strcmp(word, dictionary[j])==0)
          {
             //jesli istnieje, ustawiamy odpowiednia flage
             //i przestajemy przegladac slownik
             exists = true;
             continue;
         }

       if (exists)
       {
          //jezeli w slowniku zapisano juz dane slowo,
          //pobieramy kolejne slowo, odswiezamy flage
          //i rozpoczynamy kolejna petle
          exists = false;
          word = strtok(NULL, " ,.");
          continue;
       }

       //jesli slowo jest nowe, wpisujemy je do slownika,
       //zwikszamy licznik slow w slowniku i pobieramy kolejny wyraz
       strcpy(dictionary[i], word);
       i++;
       word = strtok(NULL, " ,.");
    }

    //wypisujemy slowa ze slownika
    for(int j=i-1; j>=0; j--)
       cout << dictionary[j] << endl;
Wklej powyższy kod do funkcji main(). Nie zapomnij wcześniej stworzyć i zainicjować tablicy tekstcaly.

Na pierwszy rzut oka przykład może wydawać się skomplikowany (nie bez kozery oznaczyliśmy go gwiazdką), ale nie martw się - już za chwilę wszystko stanie się jasne. Przyjrzyjmy się opisowi podświetlonych linijek:

  • [2] Tablica dictionary jest tablicą dwuwymiarową - będziemy ją traktować jako tablicę piętnastu napisów dziewięcioliterowych.
  • [3] Zmienna word to napis o nieokreślonej długości.
  • [5] Funkcja strtok(char* string, const char* chars_set) zwraca część napisu string do napotkania któregokolwiek ze znaków z zestawu chars_set. Zestaw ten podaje się bez oddzielania znaków spacją. Dla naszego przykładu w jego skład wchodzą spacja (" "), przecinek (",") i kropka ("."), a zwracanym przez funkcję słowem jest Tracz.
  • [7] Zmienna exist to znacznik określający, czy ostatnio pobrane słowo należy do słownika. Początkowo przyjmuje ona wartość false, ponieważ słownik jest pusty.
  • [12] Funkcja strcmp(const char* string1, const char* string2) to funkcja porównująca znak po znaku napisy string1 i string2. Jeżeli napisy są takie same, zwracana wartość to 0. Jeśli pierwszy niezgodny znak w pierwszym napisie ma większą wartość, wynik jest liczbą dodatnią, w przeciwnym zaś wypadku - ujemną.
    Wartość znaku ustalana jest na podstawie tablicy kodów ASCII, w której każdej literze przyporządkowana jest wartość liczbowa. Zgodnie ze standardem ASCII "większa" jest ta litera, która później występuje w alfabecie. Zwróć uwagę, że wielkie litery mają inny kod niż małe, przez co w naszym słowniku mogą znaleźć się słowa, które "po ludzku" zaklasyfikowalibyśmy jako takie same.
  • [26,34] Kolejne wywołanie już opisanej funkcji strtok() z nieco dziwnym pierwszym argumentem jest jak najbardziej wywołaniem prawidłowym. Gdybyśmy za każdym razem jako pierwszy argument podawali ten sam napis, ciągle zwracane byłoby to samo słowo. Podany sposób pozwala na dalsze "szatkowanie" napisu wzdłuż zestawu danych znaków, czyli w naszym wypadku na pobieranie kolejnych słów napisu.
Spróbuj poprawić nasz słownik, stosując funkcje isupper() i tolower().
Wskazówka: zastosuj fragment ostatniego listingu dla każdej litery każdego słowa napisu, na którego podstawie tworzymy słownik.

Podsumowanie

Jak zwykle w podsumowaniu mała ściągawka omawianych funkcji:

Funkcja Działanie
cstring strcpy(char* dest, const char* source) kopiuje napis z source do dest
strncpy(char* dest, const char* source, size_t n) kopiuje n początkowych znaków source do dest
strcat(char* dest, const char* source) dokleja source do napisu dest
strncat(char* dest, const char* source, size_t n) dokleja n pierwszych znaków z source do dest
strlen(const char* string) zwraca długość napisu string (bez znaku pustego)
strtok(char* string, const char* chars_set) dzieli napis string na podnapisy niezawierające znaków z chars_set
strcmp(const char* string1, const char* string2) porównuje napisy string1 i string2
cctype isalnum(char c) sprawdza, czy c jest alfanumeryczne
isalpha(char c) sprawdza, czy c jest literą
isdigit(char c) sprawdza, czy c jest cyfrą
islower(char c) sprawdza, czy c jest małą literą
isspace(char c) sprawdza, czy c jest białym znakiem
isupper(char c) sprawdza, czy c jest wielką literą
tolower(char c) konwertuje c do małej litery
toupper(char c) konwertuje c do wielkiej litery
Więcej informacji na temat powyższych funkcji oraz bibliotek znajdziesz w anglojęzycznej dokumentacji.
CC By 3.0
Copyright © 2011-2016 by Jowita Hącia [Last update: 2017-02-01 17:17:07]
This work is licensed under a Creative Commons Attribution 3.0 Unported License