7. týždeň

Strings

Štandardný vstup a výstup, polia, reťazce, práca s knižnicou string a ctype, sizeof(), ladenie programov

O čom je lab

Na tomto cvičení sa zoznámite s reťazcami v jazyku C. Taktiež spoznáte nové knižnice určené na prácu s reťazcom a vyskúšate si ich jednotlivé funkcie. Neskôr si vytvoríme zábavný nástroj na zisťovanie anagramov.

Ciele

  1. Pochopiť fungovanie polí a reťazcov v jazyku C.
  2. Zoznámiť sa s funkciami knižníc string a ctype .
  3. Osvojiť si prácu s reťazcom a implementáciu vlastných funkcií.
  4. Zoznámiť sa s operátorom sizeof() .

Postup

Krok 1: Štandardný vstup a výstup

V tomto kroku si vyskúšame, ako funguje štandardný vstup a výstup v prípade reťazca. Vyskúšame si rôzne zaužívané spôsoby implementácie.

Úloha 1.1

Vytvorte adresár ~/labs/lab6, do ktorého budete ukladať svoje kódy programov v jazyku C.

Úloha 1.2

Vo svojom obľúbenom textovom editore si vytvorte nový súbor s názvom my_name.c.

Úloha 1.3

Vytvorte program, ktorý načíta vstup od používateľa z klávesnice pomocou funkcie scanf() (s jedným %s) a vypíše: "Hello, {vaše_meno}". Tentokrát naše meno bude Janko.

Riešením môže byť nasledujúci kód:

#include <stdio.h>

#define BUFFER_SIZE 20

int main()
{
    char buffer[BUFFER_SIZE];
    printf("Enter name: ");
    scanf("%s", buffer);
    printf("Hello, %s ", buffer);
    return 0;
}

Ak ste program implementovali správne, objaví sa výpis "Hello, Janko".

Úloha 1.4

Skompilovaný program spustite znova, ale tentokrát za Vaše meno napíšte: "Janko Hraško".

CHYBA: Aj napriek tomu, že sme napísali "Janko Hraško", náš výstup je rovnaký. Prečo?

Úloha 1.5

Upravte program tak, aby vedel akceptovať aj takzvané biele znaky.

Riešením môže byť nasledujúci kód:

#include <stdio.h>

#define BUFFER_SIZE 20

int main()
{
    char buffer[BUFFER_SIZE];
    printf("Enter name: ");
    fgets(buffer, sizeof(buffer), stdin);  // read string
    printf("Hello, ");
    puts(buffer);    // display string
    return 0;
}

Použili sme funkciu puts(), ktorá vypíše reťazec a tiež sme použili funkciu fgets(), ktorá načíta reťazec od používateľa. Výsledok size(buffer) je v našom prípade 20. Čo znamená, že maximálna dĺžka reťazca pre vstup je práve 20.

Náš reťazec bol ale kratší. Ako vie funkcia, na ktorom indexe skončilo meno?

Krok 2: Ukončenie reťazca

Reťazce ako také v jazyku C neexistujú. Sú to v podstate polia znakov.

V tomto kroku si ukážeme, ako a kedy sa ukončuje reťazec v jazyku C. Koniec reťazca určuje špeciálny znak Null alebo hovorovo nazývaný terminátor '\0'.

Úloha 2.1

Znova vo svojom obľúbenom textovom editore si vytvorte nový súbor s názvom terminator.c.

Úloha 2.2

Do súboru si skopírujte nasledujúci kód.

#include <stdio.h>

int main()
{
    char hello_world[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\0'};
    char hello_world2[] = "Hello World!"; 

    return 0;
}

Úloha 2.3

Pomocou operátora sizeof() zistite veľkosť oboch polí v bajtoch a vypíšte ju na štandardný výstup.

Ak ste postupovali správne, veľkosť oboch polí je práve 13 bajtov. Prečo aj pole hello_world má viditeľne o jeden znak viac?

Krok 3: Anagram Detected

V tomto kroku si vytvoríme nástroj, ktorý by možno trocha pomohol Tomu Hanksovi vo filme The Da Vinci Code pri identifikovaní anagramu. Pri implementácii nástroja budeme používať funkcie z knižníc string a ctype, ktoré sú primárne určené na prácu s reťazcom.

Ako by mal nástroj fungovať:

  • ak zadáme hello_world a drOllWhole_, nástroj napíše The two strings are anagrams!
  • ak zadáme secure a rescue, nástroj napíše The two strings are anagrams!
  • ak zadáme Margit a TheFellOmen, nástroj napíše The two strings are not anagrams!

Úloha 3.1

Znova vo svojom obľúbenom textovom editore si vytvorte nový súbor s názvom anagram.c.

Úloha 3.2

Implementujte načítanie reťazca od používateľa. Podobne ako v predchádzajúcom kroku.

Úloha 3.3

Na štandardný vstup zadajte 2 slová oddelené medzerou.

Riešením by mohol byť nasledujúci fragment kódu:

#include <stdio.h>

#define BUFFER_SIZE 50

int main()
{
    char buffer[BUFFER_SIZE];
    fgets(buffer, sizeof(buffer), stdin);
    
    
    //....
    return 0;
}

Pre detekciu anagramu uvažujme, ako porovnávať stringy. Jedným z možných spôsobov je ich rovnaké usporiadanie znakov. Keďže znaky v jazyku C sú reprezentované číslami, reťazec si musíme upraviť. Napríklad znak 'C' má inú hodnotu v ASCII tabuľke ako znak 'c', preto je dôležité upraviť všetky alpha znaky na upper alebo lower.

Úloha 3.4

Implementujte funkciu prepare_string(), ktorá pripraví reťazec na ďalšie spracovanie.

Pomocou funkcie isalpha() z knižnice ctype identifikujte všetky alpha znaky a prekonvertujte ich na lower pomocou funkcie tolower().

Riešením by mohol byť nasledujúci fragment.

void prepare_string(char* input) {
    if (input == NULL)
    {
        return;
    }

    int len = strlen(input);

    for (int i = 0; i < len; i++)
    {
        if (isalpha(input[i]))
        {
            input[i] = tolower(input[i]);
        }
        
    }
    
}

Úloha 3.5

Rozdeľte upravený string na 2 samotné reťazce podľa medzery a uložte ich zvlášť do premenných. Môžete použiť funkciu strtok(), ktorá vytvorí takzvaný token na základe nejakého delimetera - v našom prípade medzery. Čiže v podstate rozdelí reťazec.

Riešením by bol nasledujúci fragment kódu.

// ...
prepare_string(buffer);

char delim[] = " "; // space as delimiter

char *token = strtok(buffer, delim); // first word
char first_part[strlen(token) + 1];
strcpy(first_part, token); // terminator is added by strcpy

token = strtok(NULL, delim); // second word
char second_part[strlen(token) + 1];
strcpy(second_part, token);
// ...

Úloha 3.6

V jednom slove nám však ostala medzera. Implementujte funkciu remove_spaces(), ktorá odstráni všetky medzery.

Riešením by mohol byť nasledujúci fragment:

void remove_spaces(char *str) {
    int count = 0;
    for (int i = 0; str[i]; i++) {
        if (str[i] != ' ' && str[i] != '\t' && str[i] != '\n') 
        {
            str[count++] = str[i];
        }
    }
    str[count] = '\0';
}

Úloha 3.7

Teraz, keď máme 2 samotné upravené stringy, môžeme ich usporiadať. Vytvorte funkciu are_anagrams(), ktorá usporiada znaky v oboch reťazcoch.

Keďže znaky majú aj podobu čísel, môžeme ich triedenie riešiť pomocou cyklu a vetvenia. Zotriedenie takého reťazca predstavuje nasledujúci fragment kódu.

    for (int i = 0; i < len1 - 1; i++)
    {
        for (int j = i + 1; j < len1; j++)
        {
            if (sorted_str1[i] > sorted_str1[j])
            {
                char temp = sorted_str1[i];
                sorted_str1[i] = sorted_str1[j];
                sorted_str1[j] = temp;
            }
        }
    }

Úloha 3.8

Doplňte funkciu are_anagrams() tak, aby vedela na konci oba reťazce porovnať a vyhodnotiť výsledok. Na porovnávanie reťazcov môžete použiť funkciu strcmp() z knižnice string.

Úloha 3.9

Otestuje svoje riešenie.

Anagram
Obr. 1: Anagram

Doplňujúce úlohy

Úloha A.1

Upravte program tak, aby algoritmus anagramu nebral do úvahy počet opakovaní znakov reťazca.

Doplňujúce zdroje

  1. Operátor sizeof(): c-reference - Queries size of the object or type. Used when actual size of the object must be known.
  2. How does the strtok function in C work?
  3. Ako zistiť veľkosť poľa

Video