Programowanie w języku C 2 - instrukcja do laboratorium nr 5
Autor: Mariusz Wiśniewski


  1. Formatowane wejście - funkcja scanf
    Funkcja służy do wykonywania sformatowanych operacji wejścia. Jej działanie opiera się na analizie łańcucha formatującego. Łańcuch ten zawiera tzw. sekwencje formatujące, określające typy wprowadzanych danych. Łańcuch formatujący może zawierać następujące elementy:
    • znaki puste (tj. spacja, \t, \n) - po odnalezieniu takiego znaku funkcja czyta, ale nie zapamiętuje kolejnych pustych znaków, aż do momentu napotkania pierwszego niepustego znaku.
    • znaki niepuste (tj. wszystkie pozostałe znaki ASCII z wyjątkiem %). Funkcja przeczyta, ale nie zapamięta odpowiadającego mu niepustego znaku,
    • sekwencje formatujące - funkcja przeczyta znaki i wykona konwersję do typu określonego daną sekwencją formatującą oraz zapamięta je w obszarze wskazanym odpowiednim argumentem funkcji.

    Znakom nie należącym do sekwencji formatujących muszą odpowiadać właściwe znaki w polu wejściowym. Omawiana funkcja odczyta te znaki, ale nie zapamiętają. Konstrukcja sekwencji formatującej jest podstawą budowy łańcucha formatującego. Jej ogólna postać jest następująca:

    %[*][szerokość][F|N][h|l|L]znak-typu    

    • Elementy opcjonalne sekwencji formatujących dla funkcji scanf:
      Element Działanie
      * Powoduje, że dane pole wejściowe zostanie odczytane, ale nie zapamiętane w miejscu wskazanym kolejnym wskaźnikiem z listy parametrów. Wprowadzone dane muszą być oczywiście typu zgodnego z określonym znakiem typu wraz z ewentualnym modyfikatorem F, N, h, l.
      szerokość Określa maksymalną liczbę znaków danego pola wejściowego.
      F|N Określa rozmiar nieustalonych argumentów wskaźnikowych funkcji: F - oznacza wskaźnik daleki, N - bliski. Domyślnie rozmiar wskaźników zależy od modelu pamięci.
      h|l|L Modyfikuje rozmiar typu określonego danym znakiem typu. Modyfikator h dotyczy tylko znaków typu d, i, o, u, x i powoduje potraktowanie wprowadzonej liczby jako short int. Modyfikator l użyty w odniesieniu do znaków typu d, i, o, u, x (liczby całkowite) oznacza konieczność wykonania konwersji do typu long int, dla e, f, g (liczby rzeczywiste) zaś wskazuje na konwersję typu double. Modyfikator L użyty w stosunku do znaków typu e, f, g oznacza, że powinna zostać wykonana konwersja do typu long double. Użycie omawianych modyfikatorów w stosunku do innych typów znaków nie wpływa na działanie omawianych funkcji.

    • Znaki typu dla funkcji scanf - liczby całkowite:
      Znak typu Dana wprowadzana Typ argumentu
      d
      D
      o
      O
      i
      I
      u
      U
      x
      X
      dziesiętna
      dziesiętna
      ósemkowa
      ósemkowa
      dziesiętna, ósemkowa, szesnastkowa
      dziesiętna, ósemkowa, szesnastkowa
      dziesiętna bez znaku
      dziesiętna bez znaku
      szesnastkowa
      szesnastkowa
      int *
      long *
      int *
      long *
      int *
      long *
      unsigned int *
      unsigned long *
      int *
      int *
    • Znaki typu dla funkcji scanf - liczby rzeczywiste:
      e, E
      f
      g, G
      zmiennoprzecinkowe
      zmiennoprzecinkowe
      zmiennoprzecinkowe
      float *
      float *
      float *
    • Znaki typu dla funkcji scanf - znaki:
      s
      c
      %
      łańcuch znaków
      znak
      znak '%'
      char(*)[]
      char *
      -
    • Znaki typu dla funkcji scanf - wskaźniki:
      n w przekazywanym wskaźniku typu int *, zostanie zapamiętana liczba odczytanych dotychczas znaków,
      p wskaźnik w postaci seg:ofs lub ofs, wskaźnik do zmiennej daleki lub bliski, wg domyślnego modelu pamięci.

    W czasie wprowadzania danych za daną odpowiadającą danej sekwencji formatującej zostaną uznane:
    • wszystkie znaki do pierwszego znaku pustego,
    • wszystkie znaki do pierwszego, który nie może zostać poddany konwersji określonej w sekwencji formatującej,
    • n znaków, jeśli w sekwencji formatującej określono szerokość pola.

    Do korzystania z wymienionych wcześniej znaków formatujących potrzeba jeszcze kilku dodatkowych informacji:
    • %c - powoduje odczytanie następnego znaku, również znaku pustego; aby opuścić jeden pusty i odczytać jeden niepusty znak należy użyć sekwencji %1s.
    • %Nc - argumentem funkcji dla tej sekwencji powinna być N-elementowa tablica znaków, w której zostaną umieszczone odczytane znaki,
    • %s - argumentem funkcji dla tej sekwencji powinien być wskaźnik tablicy znaków, której rozmiar powinien być o jeden większy od maksymalnej ilości wczytywanych znaków. Znak '\0' zostanie automatycznie dołączony na końcu zapamiętanego łańcucha.
    • %[zbiór-znaków] - argumentem dla tej sekwencji powinien być wskaźnik do tablicy znaków. W nawiasach należy określić zbiór, do którego powinny należeć odczytane znaki. Polem wejściowym są w tym przypadku wszystkie znaki należące do danego zbioru. Można tam wymienić:
      • pojedyncze znaki należące do zbiorów,
      • przedziały znaków.
    • Sekwencja formatująca %[0123456789] określa, że wczytywane znaki powinny być cyframi od 0..9. Tę samą informację można umieścić w postaci %[0-9]. Umieszczenie jako pierwszego w nawiasach kwadratowych znaku ^ spowoduje odwrócenie zbioru znaków określonych w dalszej części sekwencji formatującej. Określając przedział znaków zbioru należy pamiętać, że:
      • pierwszy znak określa początek, a drugi koniec przedziału, więc ten ostatni powinien mieć większy kod ASCII,
      • znak -(minus) nie może być pierwszym ani ostatnim znakiem zbioru, w takiej sytuacji znak ten zostanie potraktowany nie jako sygnalizator przedziału, lecz jako normalny znak zbioru,
      • przedziały określające zbiór muszą być rozłączne.
      Przykłady:
      %[-+*/]
      %[9-1]
      %[+a-z-A-Z]
      - symbole działań arytmetycznych +, -, *, /
      - znaki 9, 1 i -
      - znaki +, -, oraz małe i duże litery.
    • %e, %E, %f, %g, %G - wprowadzane liczby zmiennoprzecinkowe muszą spełniać następujący format:
      [=/-]dddddddd[.]dddd[E/e][+/-]ddd
      ddd - reprezentują cyfry dziesiętne, ósemkowe lub szesnastkowe. Dodatkowo rozpoznawane są sekwencje oznaczające nieskończoności (-INF i +INF) i liczby spoza zbioru liczb rzeczywistych (-NAN i +NAN).

    Funkcja scanf przerywa analizę pola i zapamiętuje informacje w następujących sytuacjach:
    • gdy przeczytano liczbę znaków określoną przez szerokość pola,
    • następny odczytany znak nie może być przekształcony do postaci określonej sekwencją formatującą,
    • następny znak nie znajduje się we wskazanym zbiorze znaków.
    Po zakończeniu analizy danego pola znak, który spowodował przerwanie odczytu uważany jest za pierwszy znak następnego pola wejściowego.

    Wykonanie funkcji scanf ulega zakończeniu, gdy:
    • następny znak pola wejściowego nie zgadza się z odpowiadającym mu znakiem łańcucha formatującego,
    • jeśli następnym znakiem pola wejściowego jest EOF,
    • jeśli wyczerpany został łańcuch formatujący.

    Przykład użycia:

    #include <stdio.h>
    #include <math.h>

    struct dane {
        char imie[15], nazwisko[20];
        unsigned wiek;
        double zarobki;
    };

    int zapisz_dane(FILE *f, struct dane *pd)
    {
        return fprintf(f, "Imie: %s\nNazwisko: %s\nWiek: %u\nZarobki: %.2lf\n------------------\n",
            pd->imie,
            pd->nazwisko,
            pd->wiek,
            pd->zarobki);
    }

    int wczytaj_dane(FILE *f, struct dane *pd)
    {
        return fscanf(f, "%*s %s\n%*s %s\n%*s %u\n%*s %le%*[\n-]",
            pd->imie,
            pd->nazwisko,
            &pd->wiek,
            &pd->zarobki);
    }

    #define TABSZ 3
    int main(void)
    {
        FILE *f;
        struct dane P[TABSZ] = {
            { "Jan", "Kowalski", 89, 1233.23 },
            { "Janina", "Kowalska", 12, 1111.12 },
            { "Karol", "Wielki", 44, 2233.11 }
        }, H;
        double x = sqrt(2);
        int i;

        f = fopen("C:\\DANE.DAT", "wt");
        for(i = 0; i < TABSZ; i++) zapisz_dane(f, &P[i]);
        fclose(f);

        f = fopen("C:\\DANE.DAT", "rt");
        for(i = 0; i < TABSZ; i++)
        {
            wczytaj_dane(f, &H);
            zapisz_dane(stdout, &H);
        }
        fclose(f);
        return 0;
    }

    W powyższym przykładzie została zdefiniowana struktura dane oraz funkcje umożliwiające wyprowadzanie jej do strumienia wyjściowego (zapisz_dane) oraz odczytanie jej ze strumienia wejściowego - funkcja wczytaj_dane. Funkcja zapisz_dane wysyła informacje zawarte w strukturze wg następującego formatu:

        Imie: <imie>
        Nazwisko: <nazwisko>
        Wiek: <wiek>
        Zarobki: <zarobki>
        ----------------------------

    Funkcja wczytaj_dane jest dostosowana do tego formatu - w wywołaniu funkcji fscanf występuje łańcuch formatujący:

        "%*s %s\n%*s %s\n%*s %u\n%*s %le%*[\n-]"

    W łańcuchu formatującym można wyróżnić cztery sekcje o podobnej budowie: najpierw jest pomijana etykieta "Imie:", "Nazwisko:", "Wiek:", "Zarobki:", a następnie występuje po etykiecie spacja, potem zostaje wczytana dana. Sekwencja formatująca %*[\n-] pomija, występujące po każdej porcji danych, separatory w postaci kilkunastu znaków -(minus). Instrukcja double x = sqrt(2); i włączenie nagłówka math.h niezbędne są dla uzyskania obsługi liczb zmiennoprzecinkowych w funkcji scanf. Brak tej instrukcji spowoduje błąd podczas wykonania programu.

  2. Zadanie do samodzielnego wykonania
    Korzystając z powyższych informacji należy przygotować:
    1. Program pobierający ze standardowego strumienia wejściowego cyfry, liczby, znaki, ciągi znaków oddzielone jednym lub więcej niż jednym znakiem białym i obliczacy ile wczytano słów, ile było razem znaków, podający największą i najmniejszą z wprowadzanych liczb. Wynik działania należy wyświetlić na ekranie.
    2. Funkcję pobierającą dane w następującej postaci:
      nazwa_parametru = "<wartość parametru>"
      Przykład:
      okno="dlugosc = "10" wysokosc = "20" tytul = "Tytuł okna" przesuwalne = "nie""
      Powyższą procedurę należy wykorzystać do stworzenia programu, który na podstawie zadanych w pliku parametrów będzie tworzył okna przy pomocy biblioteki curses, uwzględniając wartość paranetru przesuwalne poprzez dodanie lub nie dodanie takiej funkcjonalności okna. Kolejność okien powinna być zgodna z kolejnością występowania w pliku wejściowym.