Programovanie (2) v Jave
1-INF-166, LS 2016/17

Úvod · Pravidlá · Prednášky · Projekt · Netbeans · Odovzdávanie · Test a skúška
· Vyučujúcich môžete kontaktovať pomocou e-mailovej adresy E-prg.png (bude odpovedať ten z nás, kto má príslušnú otázku na starosti alebo kto má práve čas).
· Predvádzanie projektov bude v pondelok 5.6. od 9:00 do 12:00 a v utorok 6.6 od 12:00 do 13:30 (po skúške), oboje v miestnosti M217. Na termín sa prihláste v AIS. Ak robíte vo dvojici, prihlási sa iba jeden člen dvojice.
· Body zo záverečného testu sú na testovači. Poradie príkladov: P1: do šírky, P2: topologické, P3: výnimky, P4: iterátor, P5: testy, P6: strom. Bolo potrebné získať aspoň 20 bodov zo 40.
· Opravný test bude 19.6.2017 od 9:00 v miestnosti M-I. Na termín sa prihláste v AISe.
· Zapisovanie známok a osobné stretnutia ku skúške budú v utorok 13.6. 13:30-14:30 v M163 a v stredu 14.6. 14:00-14:30 v M163.


Prednáška 14

Z Programovanie
Prejsť na: navigácia, hľadanie

Organizačné poznámky

  • Termín záverečnej písomky: 16.12.2016 o 11:30 v A?
  • DÚ7 do piatka 22:00, DÚ8 zverejníme tento piatok, odovzdávanie pondelok 21.11.
  • Tento týždeň obvyklý rozvrh, budúci týždeň štvrtok sviatok 17.11., piatok voľno. Nebudú teda doplnkové cvičenia, ale utorok aj streda bežia normálne.

Dynamická alokácia poľa a matice, opakovanie

  • 1D pole: int *a = new int[n]; cin >> a[0];
  • 2D pole: int **a = new int * [n]; for(int i=0; i<n; i++) a[i] = new int[m]; cin >> a[0][1];
    • Riadky 2D poľa nemusia byť rovnako dlhé
  • 3D pole: int ***a = new int ** [n]; ...

Smerníky na struct

Smerník/ukazovateľ môže ukazovať aj na struct:

struct bod {
  int x,y;
};
bod b;  b.x = 0; b.y = 0;  /* premenna typu bod */
bod *p;          /* smernik na strukturu typu bod */
p = &b;          /* p ukazuje na bod b */
bod *p2 = new bod;  /* alokovanie noveho bodu */
p2->x = 10;      /* dve formy priradovania do sucasti structu */
(*p2).y = 20;
delete p2;        /* uvoľníme alokovanú pamäť */

Pozor, bodka má vyššiu prioritu ako hviezdička

  • preto *p.x znamená *(p.x), teda vezmeme hodnotu v p.x, interpretujeme ju ako adresu a pozrieme sa, čo je na tejto adrese
    • aký typ by muselo mať p, aby toto fungovalo?
  • väčšinou chceme skôr (*p).x, čo znamená, že interpretujeme p ako adresu na struct, v ktorom je položka x
  • keďže sa to často používa, existuje skratka p->x

Príklad: pole smerníkov na struct

  • Máme štruktúru kruh, chceme od užívateľa načítať počet kruhov a potom tieto kruhy
  • Kruhy nedáme do poľa, tam máme len smerníky na štruktúru kruh
#include <iostream>
#include <cstdlib>
using namespace std;

struct kruh {
    int x, y, r;
};

const double pi = 3.1415926536;

int main(void) {
    int n;
    cout << "Zadajte pocet kruhov: ";
    cin >> n;
    /* alokujeme pol n smernikov na kruh */
    kruh **kruhy = new kruh * [n];

    for (int i = 0; i < n; i++) {
        cout << "Zadajte suradnice stredu a polomer kruhu " << i << ": ";

        /* vytvorime novy kruh */
        kruh * novy = new kruh;
        cin >> novy->x >> novy->y >> novy->r;

        /* ulozime adresu noveho kruhu do pola */
        kruhy[i] = novy;
    }

    /* spracovavame zoznam kruhov, napr. spocitame sucet ich obsahov */
    double sucet = 0;
    for (int i = 0; i < n; i++) {
        sucet += kruhy[i]->r * kruhy[i]->r * pi;
    }
    cout << "Sucet obsahov kruhov: " << sucet << endl;

    /* na konci zmazeme kruhy aj pole */
    for (int i = 0; i < n; i++) {
        delete kruhy[i];
    }
    delete[] kruhy;
}

Všimnite si:

  • kruh **kruhy = new kruh * [n]; vytvorí pole smerníkov na kruh s maxN prvkami
  • Prístup k polomeru i-teho kruhu: kruhy[i]->r

Cvičenie: Prečo nemôžeme v programe zmeniť načítanie takto?

    kruh novy;

    for (int i = 0; i < n; i++) {
        cout << "Zadajte suradnice stredu a polomer kruhu " << i << ": ";

        /* nacitame kruh */
        cin >> novy.x >> novy.y >> novy.r;

        /* ulozime adresu noveho kruhu do pola */
        kruhy[i] = &novy;
    }

Smerníková aritmetika

  • Pole je vlastne smerník na svoj prvý prvok
  • Majme napr. pole int a[4] = {4, 3, 2, 1};
  • K i-temu prvku sa vieme dostať pomocou a[i]
  • Dá sa to však napísať aj ako *(a+i)
  • Konkrétne a+i je smerník na i-ty prvok poľa a
  • Kompilátor spočíta veľkosť jedného políčka poľa, takže vie, ako ďaleko sa posunúť, aby sa dostal na i-te políčko
  • int *b = a+2 vytvorí smerník na a[2], ale k b sa môžeme správať ako ku dvojprvkovému poľu {2, 1}
  • Podobne pri matici a[i][j] je to isté ako *(*(a+i)+j) (ak a je typu int **)

Smerníková aritmetika:

  • smerník + n: Posun smerníka o n. Ak napríklad smerník ukazuje na 0. prvok poľa, smerník + n bude ukazovať na n-tý prvok poľa.
    • Posunúť smerník o jeden prvok doprava môžeme pomocou smerník++.
  • smerník - n: Posun smerníka o n prvkov smerom k začiatku.
  • smerník1 - smerník2: Výsledkom rozdielu smerníkov je vzdialenosť miest, na ktoré ukazujú. Ak napríklad prvý smerník ukazuje na 5. prvok poľa a druhý smerník na 3. prvok poľa, ich rozdiel je 2. Smerníky musia patriť do toho istého poľa, inak bude výsledok nedefinovaný.

Porovnávanie ukazovateľov:

  • operátory: < <= > >= ==  !=
  • porovnávanie má zmysel len keď ukazovatele:
    • sú rovnakého typu
    • ukazujú do toho istého poľa

Tu je napr. zvláštny spôsob ako vypísať pole a definované vyššie:

for (int *smernik = a; smernik < a + 4; smernik++) {
     cout << "Prvok " << smernik - a << " je " << *smernik << endl;
}

Podobný kód sa ale občas používa na prechádzanie reťazcov. Napr. ak chceme zrátať počet medzier v reťazci:

int zratajMedzery(char str[]) { // mohli by sme dat aj char *str
  int pocet = 0;
  while(*str != 0) {   // kym nenajdeme ukoncovaciu nulu v retazci
     if(*str == ' ') { pocet++; }  // skontroluj znak, na ktory ukazuje smernik
     str++;                        // posun smernik na dalsi znak 
  }
  return pocet;
}

Funkcie z knižnice cstring so smerníkovou aritmetikou

  • strstr(text, vzorka) vracia smerník na char
    • NULL ak sa vzorka nenachádza v texte, smerník na začiatok prvého výskytu inak
    • pozíciu výskytu zistíme smerníkovou aritmetikou:
char *text, *vzorka;
char *where = strstr(text, vzorka);
if(where != NULL) {
  int position = where - text;
}
  • Ako by ste spočítali počet výskytov vzorky v texte?
  • Podobne strchr hľadá prvý výskyt znaku v texte

Sizeof()

Operátor sizeof() zistí "veľkosť" dátového typu v bajtoch

  • napr. pri struct nemusí byť rovné súčtu veľkostí premenných
  • presnejšie ide o vzdialenosť medzi nasledujúcimi prvkami poľa -- teda o koľko sa posunieme pri posune +1 v smerníkovej aritmetike
int i, *pi;
sizeof(*pi);   //počet bajtov potrebných na uloženie typu int 
sizeof(i);     // da sa napisat aj takto
sizeof(int);   // alebo takto

sizeof(pi);    // pocet bajtov na ulozenie smernika na int
sizeof(int *); // to iste

Práca s konzolou v Cčku: printf a scanf

  • Doteraz sme s konzolou pracovali pomocou C++ knižnice iostream cez cin a cout.
  • Teraz sa naučíme, ako sa s konzolou pracuje v klasickom Cčku pomocou príkazov printf a scanf.
  • Príkazy printf a scanf sú v knižnici cstdio.
  • Budúci týždeň si ukážeme, že podobné príkazy v Cčku existujú aj pre prácu so súbormi.
  • Knižnice jazyka C++ tiež umožňujú prácu so súbormi (podobne ako sme videli s konzolou), nebudeme ich však preberať.

printf

  • Funkcia printf vypisuje formátovaný text na konzolu.
  • Použitie: printf(format, hodnota1, hodnota2,...)
  • Formátovací reťazec obsahuje bežné znaky, ktoré sa priamo vytlačia a špeciálne formátovacie príkazy začínajúce %, ktoré určujú, ako formátovať jednotlivé hodnoty
  • Napr. predpokladajme, že x a y sú celočíslené premenné s hodnotami 10 a 20. Príkaz printf("bod (%d,%d)\n", x, y) vypíše "bod (10,20)" a koniec riadku.

Príklady formátovacích reťazcov:

  •  %c - znak (character)
  •  %s - reťazec (string)
  •  %d - celé číslo (integer)
  •  %f - reálne číslo (double)
  •  %e - reálne čislo vo vedeckej notácii napr. 5.4e7
  •  %% - vypíše znak %

Pozor, typy výrazov v zozname hodnôt musia sedieť s formátovacím reťazcom.

Formátovanie

Nastavovanie šírky a počtu desatinných miest

  • %.2f - vypíše na 2 desatinné miesta
  • %4d - ak má číslo menej ako 4 znaky, doplní vľavo medzery
  • %04d - podobne, ale dopĺňa nuly

Na predáške o reťazcoch sme videli formátovanie výsledku (zarovnanie doľava) pomocou vlastnej funkcie, ktorá číslo pripravila do pomocného poľa znakov.

int main(void) {
    char A[maxN];
    int n = 12;
    for (int i = 1; i <= n; i++) {
        int x = factorial(i);
        formatInt(i, A, 2);
        cout << A << "! = ";
        formatInt(x, A, 10);
        cout << A << endl;
    }
}

Vďaka formátovacím reťazcom v printf vieme tento program prepísať jednoduchšie.

int main(void) {
    int n = 12;
    for (int i = 1; i <= n; i++) {
        printf("%2d",i);
        printf("! = ");
        printf("%10d\n",factorial(i));
    }
}
  • Dokonca by nám v cykle stačil jediný príkaz printf("%2d! = %10d\n", i, factorial(i));
  • Podobne vieme doplniť aj úvodné nuly - napríklad, keď by sme radi v dátume doplnili deň a mesiac na dvojciferné čísla a rok na štvorciferné.
void showDate(int d, int m, int y){
    printf("%02d.%02d.%04d", d, m, y);
}

int main(void){
    showDate(1,1,1990);
}

scanf

  • Funkcia scanf(format, premenna1, premenna2,...) načítava dáta z konzoly
    • Napr. scanf("%d", &x) načíta celočíselnú hodnotu do premennej x.
    • Všimnite si, že zatiaľ čo do printf sa vkladajú priamo hodnoty (premenné), scanf potrebuje smerníky na premenné, pretože ich modifikuje.

Jednoduché načítanie mena a veku.

  char str [80];
  int i;

  printf ("Enter your family name: ");
  scanf ("%s", str);  
  printf ("Enter your age: ");
  scanf ("%d", &i);
  printf ("Mr. %s , %d years old.\n",str,i);
  • Formátovací reťazec obsahuje:
    • Špecifikáciu formátov načítávaných premenných (začínajú znakom %) - podobne ako u printf
    • Biele znaky ("whitespace" - medzery, konce riadkov, tabulátory) - funkcia číta a ignoruje všetky biele znaky pred ďalším nebielym znakom (jeden biely znak umožní ľubovoľný počet bielych znakov na vstupe)
    • Ostatné znaky formátovacieho reťazca musia presne zodpovedať vstupu

Načítavanie dátumu.

  int d,m,y;

  printf("Enter the date: ");
  scanf("%d.%d.%d", &d, &m, &y); 
  printf("Year: %d\n",y);
  • Funkcia vracia počet úspešne priradených hodnôt (načítava, kým nedôjde k chybe vstupu alebo chybe konverzie na príslušný typ premennej). V prípade chyby hneď na začiatku vracia hodnotu EOF (napríklad keď hneď na začiatku končí vstup).

Postupné sčítavanie čísel zo vstupu.

  double sum, v;

  sum = 0;
  while (scanf("%lf", &v) == 1)
    printf("%.2f\n", sum += v);

Kontrola správneho vstupu

  • Určenie, či máme na vstupe potrebný počet čísel na nejakú operáciu

Program načíta zo súboru tri double čísla a vypíše ich na obrazovku - testuje, či sú v súbore 3 čísla

int main() {
   double x, y, z;

   if(scanf("%lf %lf %lf", &x, &y, &z) == 3)
      printf("%lf \n", x + y + z);
   else 
      printf("Neboli nacitane 3 realne cisla\n.");
}
  • Kontrola celého čísla, záporného čísla, desatinného čísla, pre vhodné číselné sústavy kontrola čísla
  • Pomocou scanf však vieme robiť iba jednoduché kontroly chýb. Môže byť preto lepšie najprv načítať zo vstupu reťazec pomocou funkcie gets a tento reťazec následne spracovať. Ale o tom až na ďalšej prednáške.

Vstupy do funkcie main

  • Netbeans vám vyrobí main funkciu s nasledujúcou hlavičkou:
int main(int argc, char** argv) {
  • char **argv je pole C-čkových reťazcov a argc je počet reťazcov v tomto poli
  • Prvý z nich, argv[0], je meno samotného programu a ostatné sú argumenty programu
  • Užitočné, ak spúšťame program z príkazového riadku
  • Dá sa nastaviť v Netbeans, Properties, Run
  • Tento program jednoducho argumenty vypíše
#include <iostream>
using namespace std;

int main(int argc, char** argv) {
    for (int i = 0; i < argc; i++) {
        cout << argv[i] << endl;
    }
}

Smerníkové kuriozity

    int *a[3];                /* pole 3 smerníkov na int */
    int (*b)[3];              /* jeden smerník na pole troch intov, neinicializovaný */
    int c[3] = {0,1,2};       /* pole troch intov */
    b = &c;                   /* teraz b ukazuje na pole c */
    cout << (*b)[2] << endl;  /* pristup k prvku 2 pola c */
    cout << *b[2] << endl;    /* zle: tvarime sa, ze b je pole, pristupime k jeho prvku 2 a interpretujeme ho ako smernik */