Oznamy

Budúci týždeň veľa dôležitých vecí:

  • Pondelok prednáška: úvod do rekurzie, dôležité učivo
  • Pondelok cvičenie: práca na počítači, rekurzia, súbory
  • Pondelok 22:00: termín miniprojektu
  • Utorok cvičenie: papierová rozcvička na rekurziu, potom práca na počítači
  • Streda prednáška: pokračujeme v rekurzii
  • Streda 18:10: semestrálny test (vrátane úvodu do rekurzie), viď Test 1

Odporúčame teda začať sa pripravovať na test a odovzdať miniprojekt už tento týždeň.

Textové súbory

Na načítavanie a vypisovanie dát sme doposiaľ používali výhradne konzolu. V praxi však často potrebujeme spracovávať dáta uložené v súboroch.

Zameriame sa na súbory v textovom formáte, s ktorými sa pracuje podobne ako s konzolou.

Základy: typ FILE * a funkcie fopen, fclose, fprintf, fscanf

So súbormi sa pri použití knižnice stdio.h pracuje pomocou typu FILE * (veľkými písmenami).

  • Hviezdička (*) znamená, že ide o smerník, čo bude vysvetlené o pár týždňov.
  • Je to smerník na štruktúru typu FILE, ktorá obsahuje informácie o súbore, s ktorým sa práve pracuje.
  • Premenné pre prácu so súbormi tak možno definovať napríklad takto:
FILE *fin, *fout;

Otvorenie súboru pre čítanie

  • fin = fopen("vstup.txt", "r");
  • Otvorí súbor s názvom vstup.txt (prípadne možno zadať kompletnú cestu k súboru).
  • Ak taký súbor neexistuje alebo sa nedá otvoriť, do fin priradí špeciálna hodnota NULL.
  • Z otvoreného súboru môžeme čítať napríklad pomocou fscanf, ktorá je analógiou k scanf.
  • Napríklad fscanf(fin, "%d", &x);

Otvorenie súboru pre zápis

  • fout = fopen("vystup.txt", "w");
  • Vytvorí súbor s názvom vystup.txt. Ak už existoval, zmaže jeho obsah (keby sme vo volaní fopen namiesto "w" použili "a", pridávalo by sa na koniec existujúceho súboru).
  • Ak sa nepodarí súbor otvoriť, do fout priradí NULL.
  • Do otvoreného súboru môžeme zapisovať napr. pomocou funkcie fprintf, ktorá je analógiou k printf.
  • Napr. fprintf(fout, "%d", x);

Zatvorenie súboru

  • Po ukončení práce so súborom je ho potrebné zavrieť pomocou fclose(f);
  • Počet súčasne otvorených súborov je obmedzený.

Príklad

Nasledujúci program načíta číslo n a následne n celých čísel zo súboru vstup.txt. Do súboru vystup.txt vypíše vstupné čísla v opačnom poradí.

#include <stdio.h>
#include <assert.h>

#define NMAX 100

int main(void) {
    // otvorime vstupny subor a skontrolujeme, ze sa podarilo
    FILE *fin = fopen("vstup.txt", "r");   
    assert(fin != NULL);
    
    // nacitame pocet cisel
    int n;
    fscanf(fin, "%d", &n);
    assert(n >= 0 && n <= NMAX);

    // vytvorime pole a nacitame cisla
    int a[NMAX];
    for (int i = 0; i < n; i++) {
        fscanf(fin, "%d", &a[i]);
    }
    fclose(fin);

    // otvorime vystupny subor a skontrolujeme, ze sa podarilo
    FILE *fout = fopen("vystup.txt", "w");
    assert(fout != NULL);

    // vypiseme cisla odzadu
    for (int i = n-1; i >= 0; i--) {
        fprintf(fout, "%d ", a[i]);
    }
    fclose(fout);
}

Štandardný vstup a výstup ako súbor

So štandardným vstupom a výstupom sa pracuje rovnako ako so súborom. V stdio.h sú definované dve konštanty typu FILE * pre štandardný vstup a výstup.

FILE *stdin, *stdout;

Tie môžu byť použité tam, kde sa očakáva súbor. Napríklad volanie fscanf(stdin, "%d", &x) je ekvivalentné volaniu scanf("%d", &x).

Ten istý kód sa tak dá použiť na prácu so súbormi aj so štandardným vstupom resp. výstupom – stačí len podľa potreby nastaviť premennú typu FILE *. Typické použitie je napríklad nasledovné:

#include <stdio.h>
#include <string.h>
int main(void) {
    // do retazca str nacitame z konzoly meno suboru alebo pomlcku
    char str[101];
    scanf("%100s", str);
    // do fout otvorime subor alebo pouzijeme konzolu
    FILE *fout;    
    if (strcmp(str, "-") == 0) {
        fout = stdout;
    } else {
        fout = fopen(str, "w");
    }
    // zapiseme nieco do fout
    fprintf(fout, "Hello world!\n");
    // ak treba, zatvorime subor
    if (strcmp(str, "-") != 0) {
        fclose(fout);
    }
}    

Čítanie a zapisovanie po znakoch

Knižnica stdio.h obsahuje aj funkcie na čítanie a zapisovanie súboru po znakoch.

  • Funkcia int getc(FILE *f) načíta a vráti jeden znak zo súboru
    • Ak načítanie neprebehne úspešne, výsledkom je špeciálna konštanta EOF, ktorá je vždy rôzna od ľubovoľnej hodnoty typu char
    • Neukladajte výstupnú hodnotu funkcie getc do premennej typu char, lebo nebudete vedieť rozoznať koniec súboru.
  • Funkcia int putc(int c, FILE *f) zapíše znak c do súboru.

Príklad: kopírovanie súboru

Nasledujúci program skopíruje obsah súboru original.txt do súboru kopia.txt.

#include <stdio.h>
int main(void) {
    FILE *fin = fopen("original.txt", "r");
    FILE *fout = fopen("kopia.txt", "w");

    int c = getc(fin);
    while (c != EOF) {
        putc(c, fout);
        c = getc(fin);
    }

    fclose(fin);
    fclose(fout);
}

Cvičenie: čo robí nasledujúci program?

  • výsledkom priradenia c = getc(fin) je hodnota priradená do premennej c
#include <stdio.h>

int main(void) {
    FILE *fin = fopen("vstup.txt", "r");
    int c;
    while ((c = getc(fin)) != '\n') {
        putc(c, stdout);
    }
    putc(c, stdout);
    fclose(fin);
}

Funkcia ungetc

  • Často sa stáva, že pri načítavaní znakov nájdeme koniec práve načítavaného úseku až po načítaní znaku, ktorý už nie je žiadúce prečítať.
  • Vtedy je užitočné posunúť sa v načítavaní o jeden krok nazad.
  • Túto úlohu realizuje funkcia int ungetc(int c, FILE * f).
  • Väčšinou ako c použijeme posledne načítaný znak zo súboru f.
  • Môžeme však použiť aj iný znak, ktorý bude virtuálne pridaný na začiatok neprečítanej časti súboru. Súbor sa reálne nemení, ale pri nasledujúcom čítaní z neho sa ako prvý prečíta znak c.
  • V prípade úspechu ungetc(c,f) vráti hodnotu c; v prípade neúspechu je výstupom EOF.
  • Takéto správanie funkcie ungetc je však garantované len ak sa táto funkcia nevolá viackrát za sebou.

Príklad č. 1: Nasledujúci kus programu skonvertuje reťazec pozostávajúci zo znakov '0' až '9' na zodpovedajúcu číselnú hodnotu. Keď narazí na prvý znak, ktorý nie je cifra, vráti ho, aby sa dal použiť pri ďalšom spracovávaní.

    int hodnota = 0;
    int c = getc(stdin);
    while (c >= '0' && c <= '9') {
        hodnota = hodnota * 10 + (c - '0');
        c = getc(stdin);
    }
    ungetc(c, stdin);

Príklad č. 2: Nasledujúci program prečíta číslo pomocou funkcie fscanf, predtým však musí prečítať neznámy počet znakov '$'.

#include <stdio.h>

int main(void) {
    FILE *fin = fopen("vstup.txt", "r");
    int c = getc(fin);
    while (c == '$') {
        c = getc(fin);
    }
    ungetc(c, fin);

    int hodnota;
    fscanf(fin, "%d", &hodnota);
    printf("%d\n", hodnota);
   
    fclose(fin);
}

Čítanie a zapisovanie po riadkoch

Pripomeňme si funkcie fgets a fputs, ktoré umožňujú načítavať a zapisovať riadky zo súboru.

Funkcia fgets(str, n, f) načíta riadok zo súboru f do poľa znakov str dĺžky n.

  • Súbor f je typu FILE *.
  • Funkcia fgets načítava znaky dovtedy, kým narazí na koniec riadku (\n) alebo koniec súboru alebo kým zo súboru neprečíta n-1 znakov.
  • Prípadný znak \n na konci riadku sa nezahodí, ale pridá sa na koniec reťazca str (pokiaľ nebolo načítaných príliš veľa znakov).
  • Reťazec str vždy ukončí nulovým znakom 0.
  • Výstupom funkcie je v prípade načítania aspoň jedného znaku načítaný reťazec str; inak je výstupom NULL a reťazec str ostáva nezmenený.

Funkcia fputs(str, f) vypíše ľubovoľný reťazec str do súboru f.

  • str môže obsahovať hocikoľko koncov riadkov (aj nula)
  • pri chybe vráti EOF, inak vráti nezáporné celé číslo

Príklad: nasledujúci program načítava riadky v súbore vstup.txt a vypíše ich na konzolu, pričom na začiatok každého riadku pridá jeho poradové číslo. Funguje správne za predpokladu, že žiaden z riadkov nie je dlhší ako 100 znakov vrátane znaku '\n' na konci riadku.

#include <stdio.h>

#define NMAX 101

int main(void) {
    char str[NMAX];
    int num = 0;
    
    FILE *fin = fopen("vstup.txt", "r");
    while (fgets(str, NMAX, fin) != NULL) {
        printf("%d: ", num);  // výpis čísla riadku
        fputs(str, stdout);   // výpis samotného riadku
        num++;
    }
    fclose(fin);
}

Cvičenie:

  • Zistite, ako sa správa uvedený program, keď posledným znakom v súbore je resp. nie je znak '\n'.
  • Zistite, čo program vypíše na výstup pre súbor, ktorý obsahuje jediný riadok o 200 znakoch.

Testovanie konca súboru

Funkcia feof(subor) vráti true práve vtedy, keď niektorá funkcia čítajúca zo súboru subor už niekedy pokúšala čítať za koncom súboru.

  • Použitie uvidíme nižšie.

Prístupy k spracovaniu textového vstupu

Časté schémy spracovania textového vstupu zo súboru alebo konzoly:

  • Pomocou fscanf načítavame jednotlivé čísla, slová a pod. (vhodné, keď všetky biele znaky považujeme za ekvivalentné oddeľovače)
  • Čítame po znakoch pomocou getc (pomerne univerzálne, ale niekedy prácne)
  • Čítame po riadkoch pomocou fgets do reťazca, potom reťazec spracovávame (vhodné, keď riadok je ucelená časť súboru, ktorá nás zaujíma)

Niekedy je užitočné tieto prístupy aj kombinovať, viď príklad nižšie.

Príklad: chceme nájsť dĺžku najdlhšieho riadku v súbore

  • Prvá možnosť je čítanie riadkov do reťazca a ich spracovanie (problém, ak je riadok príliš dlhý)
  • Druhá možnosť je čítať súbor po znakoch, pričom si potrebujeme udržiavať “stav”: koľko písmen sme už videli v aktuálnom riadku
#include <stdio.h>
#include <string.h>

#define NMAX 100

int main(void) {
    FILE *fin = fopen("vstup.txt", "r");
    int maxDlzka = 0;
    char str[NMAX];
    while (fgets(str, NMAX, fin) != NULL) {
        int dlzka = strlen(str);
        if (dlzka > maxDlzka) {
            maxDlzka = dlzka;
        }
    }
    fclose(fin);
    printf("Najdlhsi riadok ma dlzku %d\n", maxDlzka);
}
#include <stdio.h>

int main(void) {
    FILE *fin = fopen("vstup.txt", "r");
    int maxDlzka = 0;
    int dlzka = 0;
    int c = getc(fin);
    while (c != EOF) {
        dlzka++;
        if (c == '\n') {
            if (dlzka > maxDlzka) {
                maxDlzka = dlzka;
            }
            dlzka = 0;
        }
        c = getc(fin);
    }
    if (dlzka > maxDlzka) { // Posledny riadok nemusi koncit symbolom \n.
        maxDlzka = dlzka;
    }
    fclose(fin);
    printf("Najdlhsi riadok ma dlzku %d\n", maxDlzka);
}

Cvičenie: čo ak chceme zistiť, koľký riadok v súbore bol ten najdlhší?

Kontrola správnosti vstupu vo scanf a fscanf

Funkcie scanf a fscanf vracajú počet úspešne načítaných hodnôt zo vstupu.

  • V prípade chyby hneď na začiatku vstupu tak napríklad vráti 0.
  • Ak hneď na začiatku narazí na koniec vstupu, vráti hodnotu EOF
  • Vstup z konzoly sa dá ukončiť pod Linuxom ako Ctrl+D resp. pod Windowsom ako Ctrl+Z a Enter

Príklad: zadávanie dátumu vo formáte deň.mesiac.rok s kontrolou vstupu:

#include <stdio.h>

void vypisDatum(int d, int m, int r) {
    printf("%02d.%02d.%04d\n", d, m, r);
}

int main(void) {
    int d, m, r;
    printf("Zadaj datum ako den.mesiac.rok: ");
    int uspech = scanf("%d.%d.%d", &d, &m, &r);
    if (uspech == 3) {
        printf("Datum je ");
        vypisDatum(d, m, r);
    } else {
        printf("Nebol zadany korektny datum.\n");
        printf("Pocet uspesne nacitanych poloziek: %d.\n", uspech);
    }
}
  • V tomto programe sme vo funkcii scanf použili špecifikáciu konverzie %d.%d.%d, ktorá očakáva tri celé čísla oddelené bodkami. Potom kontrolujeme, či sa podarilo načítať všetky tri čísla.
  • Vo funkcii printf sme použili špecifikáciu konverzie %02d a %04d, ktorá zabezpečí, že čísla budú vypísané s minimálnou dĺžkou 2 resp. 4 znaky, pričom sa doplnia nuly zľava.
  • Viac o funkciách scanf a printf si môžete prečítať na konci týchto poznámok.

Ďalší program počíta súčet postupne zadávaných čísel, až kým je zadané nekorektné číslo alebo koniec súboru:

#include <stdio.h>

int main(void) {
    double sum = 0;
    double x;
    while (scanf("%lf", &x) == 1) {
         sum += x;
    }
    printf("Sucet je %.2f\n", sum);
}

Spracovanie vstupu pozostávajúceho z postupnosti čísel

Často na vstupe očakávame postupnosť číselných hodnôt oddelených bielymi znakmi. Pozrime sa na tri obvyklé možnosti, ako môže byť takýto vstup zadaný a spracovaný pomocou funkcie fscanf.

Formát 1: N (počet čísel) a následne N ďalších čísel. Pre jednoduchosť často používame na tomto predmete, ale je menej častý v praxi.

#include <stdio.h>
#include <assert.h>

#define NMAX 100

int main(void) {
    FILE *f;
    int a[NMAX], n, uspech;

    f = fopen("vstup.txt", "r");
    assert(f != NULL);

    uspech = fscanf(f, "%d ", &n);
    assert(uspech == 1 && n >= 0 && n <= NMAX);

    for (int i = 0; i < n; i++) {
        uspech = fscanf(f, "%d ", &a[i]);
        assert(uspech == 1);
    }
    fclose(f);

    // tu pride spracovanie dat v poli a
}

Formát 2: postupnosť čísel ukončená číslom -1 alebo inou špeciálnou hodnotu.

    // otvorime subor f ako vyssie
    n = 0;
    int x;
    uspech = fscanf(f, "%d ", &x);
    assert(uspech == 1);
    while (x != -1) {
        assert(n < NMAX);
        a[n] = x;
        n++;
        uspech = fscanf(f, "%d ", &x);
        assert(uspech == 1);
    }
    // zatvorime subor a spracujeme data

Formát 3: čísla, až kým neskončí súbor (najtypickejší prípad v praxi).

Priamočiary prístup nefunguje vždy správne:

    // otvorime subor f ako vyssie
    n = 0;
    while (!feof(f)) {
        assert(n < NMAX);
        uspech = fscanf(f, "%d", &a[n]);
        assert(uspech == 1); // bude tu padat
        n++;
    }
    // zatvorime subor a spracujeme data
  • Tento program nefunguje, ak po poslednom čísle v súbore nasleduje ešte koniec riadku (čo je obvyklé).
  • Pri načítaní tohto čísla skončíme na symbole konca riadku, fscanf sa teda nepokúsi čítať za koncom súboru.
  • Spustí sa teda ďalšie opakovanie cyklu, v ktorom sa už ale nepodarí ďalšie číslo načítať a assert ukončí program s chybovou hláškou.
  • Aj bez príkazu assert by sme mali problém, že do poľa by sa uložila nezmyselná hodnota.

Tento nedostatok môžeme napraviť napríklad tak, že vo volaní funkcie fscanf dáme vo formátovacom reťazci za %d medzeru. Tá sa bude pokúšať preskočiť všetky biele znaky až po najbližší nebiely; pritom natrafí na koniec súboru a feof(f) už bude vracať true.

#include <stdio.h>
#include <assert.h>

#define NMAX 100

int main(void) {
    FILE *f;
    int a[NMAX], n, uspech;

    f = fopen("vstup.txt", "r");
    assert(f != NULL);

    n = 0;
    while (!feof(f)) {
        assert(n < NMAX);
        uspech = fscanf(f, "%d ", &a[n]); // medzera za %d
        assert(uspech == 1);
        n++;
    }
    fclose(f);

    // tu pride spracovanie dat v poli a
}

Cvičenie: upravte úryvky programu vyššie tak, aby pracoval s číslami typu double.

Zhrnutie práce so súbormi

  • Ukázali sme si prácu so súbormi pomocou knižnice stdio.h
  • Súbor je reprezentovaný typom FILE *.
  • Na otváranie a zatváranie súborov slúžia funkcie fopen, fclose.
  • Na vypísanie kombinácie čísel a textov slúži funkcia fprintf, čísla a slová načítavame pomocou fscanf.
  • Po znakoch pracujeme funkciami putc a getc, niekedy sa zíde aj ungetc.
  • Po riadkoch pracujeme pomocou fgets a fputs.
  • V programoch, ktoré sa majú reálne používať, je vhodné kontrolovať, či tieto operácie úspešne zbehli. Tiež pozor na to, aby pri načítaní reťazcov nenastalo pretečenie poľa.
  • K niektorým funkciám existujú aj verzie pre konzolu, napr. printf a scanf.
  • S konzolou tiež môžeme pracovať pomocou premenných stdin, stdout.

Ďalšie možnosti

  • Okrem textových súborov existujú aj binárne, kde nie je číslo zapísané ako postupnosť cifier, ale priamo ako bajty.
    • Binárne súbory majú často menšiu veľkosť a rýchlejšie sa s nimi pracuje, ťažšie však ručne skontrolujete, čo v súbore je.
    • V knižnici stdio.h existujú na prácu s binárnymi súbormi funkcie fwrite a fread.
  • V mnohých programovacích jazykoch existujú knižnice na načítavanie a zapisovanie často používaných formátov súborov.

Príklad kombinácie čítania po znakoch a po číslach

Príklad: Máme súbor s číslami oddelenými bielymi znakmi (medzery, tabulátory, konce riadkov,…), pričom medzi dvoma číslami môže byť aj viac ako jeden oddeľovač. Chceme spočítať súčet čísel na každom riadku.

  • Nepríjemná kombinácia rozlišovania koncov riadku od iných bielych znakov a čítania formátovaných hodnôt (čísel)
  • Môžeme prečítať riadok do reťazca a rozložiť na čísla (napr. funkciou sscanf a so špecifikáciou konverzie %n, ktorá uloží počet načítaných znakov).
  • Program nižšie však používa kombináciu getc, ungetc a fscanf
    • Biele znaky spracúva pomocou funkcie getc. Ak je niektorý z týchto znakov koncom riadku, vypíše zistený súčet čísel.
    • Po nájdení nebieleho znaku ho vráti do vstupného prúdu funkciou ungetc, prečíta číslo pomocou funkcie fscanf a aktualizuje súčet.
    • Na zistenie, či je prečítaný znak biely, využíva program funkciu isspace z knižnice ctype.h.
    • Program predpokladá, že aj posledný riadok vstupného súboru je ukončený symbolom \n.
#include <stdio.h>
#include <ctype.h>

int main(void) {
    FILE *fin = fopen("vstup.txt", "r");
    int sucet = 0;
    int hodnota;
    while (!feof(fin)) {
        int c = getc(fin);
        // Precitaj biele znaky po najblizsi nebiely
        while (c != EOF && isspace(c)) {
            if (c == '\n') {   // Na konci riadku vypis sucet
                printf("Sucet %d\n", sucet);
                sucet = 0;
            }
            c = getc(fin);
        }
        if (c == EOF) { // Koniec suboru
            break;
        }
        ungetc(c, fin);
        fscanf(fin, "%d", &hodnota);
        sucet += hodnota;
    }
    fclose(fin);
}

Cvičenia:

  • Čo program spraví, ak vstup obsahuje aj prázdne riadky?
  • Upravte program, aby pracoval správne aj v prípade, že posledný riadok nie je ukončený symbolom \n.
    • Čo vlastne chceme považovať za posledný riadok?
  • Upravte program, aby na výstupe vypisoval aj čísla na riadkoch oddelené medzerami.

Jednoduché šifrovanie: Caesarova šifra

Prácu so súbormi si teraz precvičíme na jednoduchej šifre. Caesarova šifra je šifra, pri ktorej sa každé písmeno vstupného reťazca posunie cyklicky o K miest v abecednom poradí, kde K je zadaný parameter šifry.

  • Napríklad pre K=2 sa písmeno A zmení na C, písmeno b sa zmení na d a písmeno Z sa zmení na B.
  • Ukážeme si jej použitie pre anglickú abecedu (t. j. znaky 'a' až 'z' a 'A' až 'Z' bez diakritiky); je ju ale možné upraviť napríklad aj tak, aby pracovala s ASCII kódmi.

Napríklad pre K=3:

Pôvodný text:   HelloWorld
Šifrovaný text: KhoorZruog

Nasledujúci program zašifruje súbor.

#include <stdio.h>
#include <assert.h>

void encryptCaesar(FILE *fin, FILE *fout, int shift) {
    assert(shift >= 0 && shift <= 25);
    int c;
    while ((c = getc(fin)) != EOF) {
        if ((c >= 'A') && (c <= 'Z')) {
            c = c + shift;
            if (c > 'Z') {
                c -= 26;
            }
        } else if ((c >= 'a') && (c <= 'z')) {
            c = c + shift;
            if (c > 'z') {
                c -= 26;
            }
        }
        putc(c, fout);
    }
}

int main(void) {
    int shift;
    scanf("%d", &shift);  // nacitame parameter o kolko posuvat pismena
    
    FILE *fin = fopen("plaintext.txt", "r");
    FILE *fout = fopen("ciphertext.txt", "w");
    
    encryptCaesar(fin, fout, shift);
    
    fclose(fin);
    fclose(fout);
}
  • Čo program spraví so znakmi, ktoré nie sú písmená?
  • Dešifrovanie by fungovalo podobne, akurát by sa posun odpočítaval a ako špeciálny prípad by sa riešilo, keď nový znak má kód menší ako ‘a’. Môžeme ale tiež použiť program na šifrovanie a posun 26-K, kde K je posun použitý na šifrovanie.
  • Caesarova šifra sa dá ľahko prelomiť.
  • O šifrách, ktoré sa v súčasnosti používajú, sa môžete dozvedieť vo vyšších ročníkoch na predmete Kryptológia.

Ďalšie detaily funkcií printf a fprintf

Veľkou výhodou týchto funkcií je, že vieme v jednom volaní vypísať niekoľko hodnôt rôznych typov, pričom formát výpisu je možné rôzne nastaviť.

Vo všeobecnosti vyzerá volanie týchto funkcií nasledovne:

printf(format, hodnota1, hodnota2, ...);
fprintf(subor, format, hodnota1, hodnota2, ...);
  • Prvý argument printf je formátovací reťazec.
  • Bežné znaky z formátovacieho reťazca sa priamo vypíšu na výstup.
  • Symbol % začína takzvanú špecifikáciu konverzie a má za následok vypísanie ďalšieho argumentu funkcie (prvého, ktorý sa nepoužil).
  • V jednoduchých príkladoch za znakom % nasleduje znak reprezentujúci typ vypísanej hodnoty.
  • Pozor, typy jednotlivých argumentov musia byť v súlade s formátovacím reťazcom.

  • %d: celé číslo (typ int).
  • %ld: dlhé celé číslo (typ long int).
  • %f: reálne číslo (typ double).
  • %e: reálne číslo vo vedeckej notácii, napr. 5.4e7 (typ double).
  • %c: znak (typ char).
  • %s: reťazec (typ char *).
  • %%: vypíše samotný znak %.

Formátovanie výstupu

  • Formát vypísania daného argumentu možno upraviť nepovinnými parametrami medzi symbolom % a znakom konverzie.
  • Napríklad:
    • %.2f: vypíše reálne číslo na 2 desatinné miesta,
    • %4d: ak má celé číslo menej ako 4 cifry, doplní vľavo medzery,
    • %04d: podobne, ale dopĺňa nuly.

Nasledujúci program vypíše zadaný dátum vo formáte typu 02.01.2027:

#include <stdio.h>

void vypisDatum(int d, int m, int r) {
    printf("%02d.%02d.%04d\n", d, m, r);
}

int main(void) {
    vypisDatum(2, 1, 2027);
}

Celá špecifikácia konverzie pozostáva z nasledujúcich častí:

  • Z povinného úvodného znaku %.
  • Z nepovinných príznakov, napríklad -, ktorého použitie vyústi v zarovnanie vypisovaného textu vľavo (bez jeho použitia sa text zarovná vpravo). Ďalšími príznakmi sú napríklad 0 (dopĺňanie núl naľavo), + (vypíše znamienko + pri kladných číslach), atď.
  • Z nepovinného celého čísla udávajúceho minimálnu šírku výpisu (minimálny počet „políčok”, do ktorých sa text vypíše).
  • Z nepovinnej bodky nasledovanej celým číslom udávajúcim presnosť výpisu (pri reálnych číslach napríklad počet desatinných miest; presnosť má však svoju interpretáciu aj pri iných typoch dát).
  • Z nepovinného modifikátora l, ll, alebo h pre long, long long, resp. short.
  • Z povinného symbolu konverzie (napr. d, f, s, …).

Ďalšie detaily funkcií scanf a fscanf

Tieto funkcie vedia načítať viacero hodnôt naraz. Ich použitie:

scanf(format, &premenna1, &premenna2, ...) 
fscanf(subor, format, &premenna1, &premenna2, ...) 

Pomocou scanf možno načítať aj viacero premenných naraz:

#include <stdio.h>

void vypisDatum(int d, int m, int r) {
    printf("%02d.%02d.%04d\n", d, m, r);
}

int main(void) {
    int d, m, r;
    printf("Zadaj den, mesiac a rok: ");  
    scanf("%d %d %d", &d, &m, &r);        
    vypisDatum(d, m, r);                    
}

Formátovací reťazec sa v scanf interpretuje nasledovne:

  • Špecifikácia typu načítavaných premenných je podobná ako pri funkcii printf.
    • %d načíta int
    • %lf načíta double (pozor, tu je rozdiel od printf, kde sa double vypisuje pomocou %f)
    • %s načíta reťazec, konkrétne jedno slovo, t.j. postupnosť nebielych znakov. Ako argument sa zadá dostatočne veľké pole prvkov typu char.
    • %100s načíta slovo, ale najviac 100 znakov. Pole má mať veľkosť aspoň 101.
    • %c načíta jeden znak. Na rozdiel od všetkých predchádzajúcich typov, tu sa nepreskakujú biele znaky pred prvým nebielym.
  • Biele znaky (angl. whitespace, t.j. medzery, konce riadkov, tabulátory) vo formátovacom reťazci spôsobia, že funkcia scanf číta a ignoruje všetky biele znaky pred ďalším nebielym znakom. Jeden biely znak vo formátovacom reťazci tak umožní ľubovoľný počet bielych znakov na vstupe.
  • Ostatné znaky formátovacieho reťazca musia presne zodpovedať vstupu.

Nasledujúci príkaz tak napríklad načíta dátum vo formáte deň.mesiac.rok:

scanf("%d.%d.%d", &d, &m, &r);