Nizovi: mnogo vrijednosti pod jednim imenom

Nizovi u MakeCodeu i Arduino programu: indeksi od nule, petlja kroz niz, dvodimenzionalni nizovi, sizeof, memorija i greška izvan granica niza.

Arduino Info Micro:bit

Red kutijica

Varijabla je kutijica s imenom u koju program spremi jedan podatak. Ali što ako program mora pamtiti mnogo sličnih podataka: bodove četiri igrača, pinove četiri LED-ice ili znakove svih 16 tipki tipkovnice? Šesnaest varijabli s gotovo istim imenima teško je pisati i čitati.

Zato postoji niz: red kutijica s jednim zajedničkim imenom. Svaka kutijica ima svoj redni broj, indeks, a indeksi počinju od nule:

Četiri spojene kutijice s brojevima 12, 7, 15 i 9, a ispod njih indeksi od 0 do 3

U nizu na slici su bodovi četiri igrača. Prvi igrač ima indeks 0, a zadnji indeks 3, iako su igrača četiri. To je kao s katovima zgrade: prizemlje je nulti kat, pa je četvrti kat od dna treći kat.

U jednom nizu sve vrijednosti su iste vrste: sve su brojevi, ili su sve znakovi.

Kako se nizovi koriste na micro:bitu, piše u odjeljku Nizovi u MakeCodeu, a za Arduino radionicu je odjeljak Nizovi u Arduino programu.

Nizovi u MakeCodeu

MakeCode niz zove array. Blokovi za nizove su u kategoriji Arrays, koja se otvori kad klikneš Advanced:

  • set list to array of 1 2 3 napravi niz s imenom list i u njega spremi tri broja;
  • list get value at 0 pročita prvu vrijednost u nizu;
  • length of array list kaže koliko je vrijednosti u nizu.

Nizovi u Arduino programu

Od ovog mjesta stranica je za Arduino radionicu.

Kako napraviti niz

Niz napravimo kao varijablu, ali iza imena u uglatim zagradama napišemo koliko vrijednosti ima, a vrijednosti u vitičastim zagradama:

int bodovi[4] = {12, 7, 15, 9};       // četiri broja tipa int
const int ledice[] = {2, 3, 4, 5};    // broj se smije izostaviti: prevoditelj ih sam prebroji
char znakovi[3] = {'A', 'B', 'C'};    // tri znaka

Do pojedine vrijednosti, elementa niza, dolazimo po indeksu u uglatim zagradama:

Serial.println(bodovi[0]);   // prvi element: 12
Serial.println(bodovi[3]);   // zadnji element: 9
bodovi[2] = 20;              // treći element sada je 20

Nekoliko pravila:

  • Ako napišeš manje vrijednosti nego što niz ima elemenata, ostali su 0: int a[5] = {1, 2}; je 1, 2, 0, 0, 0.
  • Niz napisan iznad setup() bez vrijednosti pun je nula, kao bilaPritisnuta[4][4] u lekciji Mnogo prekidača na malo pinova.
  • Broj elemenata zadan je kad niz napraviš. Niz poslije ne može narasti ni se smanjiti.
  • Niz ne možeš kopirati znakom = (a = b; je greška), niti dva niza usporediti s ==. Elemente kopiraj ili usporedi jedan po jedan, u petlji.

Petlja kroz niz

Niz je najkorisniji s petljom for: ista naredba izvede se za svaki element, a indeks je brojač petlje. Ovako svi pinovi iz niza ledice postanu izlazi:

const int ledice[] = {2, 3, 4, 5};
const int BROJ_LEDICA = 4;

void setup() {
  for (int i = 0; i < BROJ_LEDICA; i++) {   // i ide od 0 do 3
    pinMode(ledice[i], OUTPUT);
  }
}

Uvjet je i < BROJ_LEDICA, a ne i <= BROJ_LEDICA: zadnji indeks je za jedan manji od broja elemenata.

Broj elemenata ne moraš pisati sam. sizeof kaže koliko bajtova nešto zauzima: cijeli niz od četiri int-a zauzima 8 bajtova, a jedan element 2. Kad ih podijeliš, dobiješ broj elemenata, i on ostane točan kad nizu dodaš ili makneš vrijednost:

const int BROJ_LEDICA = sizeof(ledice) / sizeof(ledice[0]);   // 8 / 2 = 4

Probaj sam: bodovi igrača

Ovaj program ispiše bodove četiri igrača, njihov zbroj i pobjednika. Učitaj ga i otvori serijski monitor:

const int BROJ_IGRACA = 4;
int bodovi[BROJ_IGRACA] = {12, 7, 15, 9};   // bodovi[0] je prvi igrač, bodovi[3] zadnji

void setup() {
  Serial.begin(9600);

  int zbroj = 0;
  int najbolji = 0;                          // indeks igrača s najviše bodova
  for (int i = 0; i < BROJ_IGRACA; i++) {    // i ide od 0 do 3
    Serial.print("Igrač ");
    Serial.print(i + 1);                     // ljudi broje od 1
    Serial.print(": ");
    Serial.println(bodovi[i]);
    zbroj = zbroj + bodovi[i];
    if (bodovi[i] > bodovi[najbolji]) {
      najbolji = i;
    }
  }

  Serial.print("Ukupno: ");
  Serial.println(zbroj);
  Serial.print("Pobjednik: igrač ");
  Serial.println(najbolji + 1);
}

void loop() {
}

U serijskom monitoru piše:

Igrač 1: 12
Igrač 2: 7
Igrač 3: 15
Igrač 4: 9
Ukupno: 43
Pobjednik: igrač 3

Program pamti indeks najboljeg igrača, a ne njegove bodove: iz indeksa zna i bodove (bodovi[najbolji]) i koji je to igrač. Ljudi broje od 1, pa programu dodamo 1 tek kad ispisuje.

Dodaj petog igrača: promijeni BROJ_IGRACA u 5 i dodaj njegove bodove u vitičaste zagrade. Ostatak programa ostaje isti, i to je cijela prednost niza.

Dvodimenzionalni niz

Niz može imati i retke i stupce, kao tablica. Tada ima dva indeksa: prvi je redak, a drugi stupac. Tako su u lekciji Mnogo prekidača na malo pinova spremljeni znakovi tipkovnice 4 × 4:

const char znakovi[4][4] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};

Tablica 4 puta 4 sa znakovima tipkovnice. Lijevo su indeksi redaka od 0 do 3, a gore indeksi stupaca od 0 do 3. Redak 1 i stupac 2 su označeni, a znak 6 na njihovom križanju jače
znakovi[1][2] je znak u retku 1 i stupcu 2, dakle '6'. Kroz dvodimenzionalni niz idemo petljom u petlji: vanjska ide kroz retke, a unutarnja kroz stupce.

Izvan granica niza

U nizu od četiri elementa postoje samo indeksi od 0 do 3. Što se dogodi s bodovi[4]?

Memorija kao red kućica: siva kućica, četiri plave kućice niza s brojevima 12, 7, 15 i 9 i indeksima 0 do 3, narančasta kućica druge varijable i opet siva. Crvena strelica s natpisom [4] = 0 pokazuje na narančastu kućicu, u kojoj je prekrižen broj 3 i upisana crvena 0, a pokraj je crveni križić

C++ ne provjerava je li indeks u granicama niza. Elementi niza su u memoriji jedan do drugoga, a iza zadnjeg je obično neka druga varijabla. bodovi[4] zato pročita ono što je u memoriji iza niza, a bodovi[4] = 0 u tu drugu varijablu upiše 0.

Nema poruke o grešci

Prevoditelj ne javi grešku, a program se ne zaustavi. Samo se neka druga varijabla tiho promijeni, pa program počne raditi čudno, a uzrok je na sasvim drugom mjestu. Najčešći uzrok je uvjet petlje i <= 4 umjesto i < 4.

Kad program radi čudno, a koristi nizove, prvo provjeri uvjete petlji i indekse.

Niz i funkcija

Niz možemo predati funkciji, ali funkcija ne zna koliko elemenata niz ima. Zato joj broj elemenata predamo kao drugi parametar:

int zbroji(const int brojevi[], int koliko) {
  int zbroj = 0;
  for (int i = 0; i < koliko; i++) {
    zbroj = zbroj + brojevi[i];
  }
  return zbroj;
}

Poziv je zbroji(bodovi, BROJ_IGRACA). const kaže da funkcija niz samo čita. Bez const funkcija bi smjela mijenjati elemente, i to bi bili elementi pravog niza, a ne njihova kopija.

Nizovi i memorija

Arduino Uno ima samo 2 KB (2048 bajtova) memorije za varijable, a nizovi je brzo napune: niz od 1000 int-ova zauzme 2000 bajtova, i za sve ostalo ostane samo 48. Zato:

  • Odaberi najmanji tip koji je dovoljan. Za brojeve od 0 do 255 dovoljan je byte, koji zauzima upola manje od int-a.
  • Pazi na veliku tablicu koja se ne mijenja. Na Unu je i takva tablica u memoriji za varijable, osim ako je spremiš u memoriju za program s PROGMEM (pogledaj savjete na stranici Arduino IDE).
  • Pri prevođenju Arduino IDE ispiše koliko memorije zauzimaju globalne varijable: Global variables use … bytes.

Tekst je niz znakova

I tekst je niz: char ime[] = "Ana"; je niz znakova 'A', 'n' i 'a'. Ali taj niz ima četiri elementa, a ne tri. Na kraju je još jedan znak, '\0', broj 0, koji označava gdje tekst završava. Tako Serial.println(ime) zna kada treba stati.

Nizove smo koristili u lekciji Mnogo prekidača na malo pinova, za pinove redaka i stupaca, znakove tipki i stanje svake tipke. U lekciji Igra reakcije jedan od izazova sprema frekvencije fanfare u niz.

Što smo naučili?

  • Niz je red varijabli s jednim imenom. Svaka ima svoj indeks, a indeksi počinju od 0, pa je zadnji indeks za jedan manji od broja elemenata.
  • U MakeCodeu nizovi su u kategoriji Arrays, pod Advanced. U Arduino programu niz pišemo s uglatim zagradama: int bodovi[4] = {12, 7, 15, 9};.
  • S petljom for ista naredba izvede se za svaki element niza. sizeof(niz) / sizeof(niz[0]) daje broj elemenata.
  • Dvodimenzionalni niz ima retke i stupce: znakovi[1][2] je redak 1, stupac 2.
  • C++ ne provjerava granice niza. Indeks izvan niza tiho promijeni neku drugu varijablu.
  • Nizovi brzo napune 2 KB memorije Una, pa za njih biramo najmanji dovoljan tip.

Mali kviz

1. Niz ima šest elemenata. Koji je indeks prvog, a koji zadnjeg?
Prvi ima indeks 0, a zadnji 5.
2. Koliko je bodovi[1] u nizu int bodovi[4] = {12, 7, 15, 9};?
  1. Indeks 1 je drugi element, jer brojimo od nule.
3. Što ne valja u petlji for (int i = 0; i <= 4; i++) za niz od četiri elementa?
Petlja ide i kroz i = 4, a takvog elementa nema. Program tada pročita ili promijeni memoriju iza niza. Ispravan uvjet je i < 4.
4. Što je u nizu int a[4] = {5};?
5, 0, 0, 0. Elementi bez vrijednosti su 0.
5. Koliko bajtova zauzima niz byte slika[8], a koliko niz long vremena[8] na Unu?
byte ima jedan bajt, pa prvi niz zauzima 8 bajtova. long ima četiri, pa drugi zauzima 32 bajta.
6. Koji je znak znakovi[3][0] u tablici tipkovnice?
'*'. Redak 3 je zadnji redak ('*', '0', '#', 'D'), a stupac 0 prvi znak u njemu.
7. Zašto funkciji osim niza predajemo i broj njegovih elemenata?
Funkcija ne zna koliko je niz velik. Bez broja elemenata ne bi znala gdje treba stati.
8. Koliko elemenata ima niz char ime[] = "Ana";?
Četiri: tri slova i znak '\0' na kraju, koji označava kraj teksta.