Strukture u jeziku C

Lekcija 14 od 15 · C

Struktura (struct) spaja više podataka različitih tipova u jednu celinu – npr. ime, razred i prosek učenika. Umesto tri odvojena niza imamo jedan niz učenika. U ovoj lekciji su definisanje, pristup poljima, nizovi struktura, slanje u funkcije i sortiranje.

Pre nego što počneš: potrebno predznanje – pokazivači, stringovi, funkcije.
Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su u jeziku C (C11) i provereno se prevode (gcc -Wall) i daju navedeni izlaz.

Definisanje strukture i pristup poljima

Struktura se definiše van funkcije main. Svaki deo strukture zove se polje (član). Polju se pristupa tačkom: t.x.

#include <stdio.h>

/* nov tip: tačka u ravni */
struct Tacka {
    int x;
    int y;
};

int main(void) {
    struct Tacka a;              /* promenljiva tipa struct Tacka */
    a.x = 3;
    a.y = 4;

    struct Tacka b = {1, 2};     /* inicijalizacija redom polja */
    struct Tacka c = {.y = 7, .x = 5};   /* ili po imenima polja */

    printf("a = (%d, %d)\n", a.x, a.y);
    printf("b = (%d, %d)\n", b.x, b.y);
    printf("c = (%d, %d)\n", c.x, c.y);

    b = a;                       /* dodela kopira sva polja */
    printf("posle b = a: (%d, %d)\n", b.x, b.y);
    return 0;
}

Strukture se mogu dodeljivati (b = a), ali se ne mogu porediti sa == – poredi se polje po polje.

Kraće ime tipa: typedef

Da ne bismo stalno pisali struct Tacka, uvodi se kraće ime pomoću typedef:

#include <stdio.h>

typedef struct {
    char ime[30];
    int razred;
    double prosek;
} Ucenik;                        /* sada je Ucenik ime tipa */

int main(void) {
    Ucenik u;
    scanf("%29s %d %lf", u.ime, &u.razred, &u.prosek);
    printf("%s, %d. razred, prosek %.2f\n", u.ime, u.razred, u.prosek);
    return 0;
}

Za tekstualno polje (char ime[30]) u scanf se ne piše & – ime niza je već adresa. Broj 29 čuva mesto za završni znak '\0' (vidi Stringovi u jeziku C).

Niz struktura

Najčešća upotreba: niz struktura umesto više paralelnih nizova. Element niza je cela struktura, a polju se pristupa sa niz[i].polje.

#include <stdio.h>

typedef struct {
    char ime[30];
    int poeni;
    int vreme;                   /* u minutima */
} Takmicar;

int main(void) {
    int n;
    Takmicar t[100];
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
        scanf("%29s %d %d", t[i].ime, &t[i].poeni, &t[i].vreme);

    /* najbolji: više poena; kod istih poena – kraće vreme */
    int best = 0;
    for (int i = 1; i < n; i++)
        if (t[i].poeni > t[best].poeni ||
            (t[i].poeni == t[best].poeni && t[i].vreme < t[best].vreme))
            best = i;

    printf("Pobednik: %s (%d poena, %d min)\n", t[best].ime, t[best].poeni, t[best].vreme);
    return 0;
}

Uporedi sa rešenjem bez struktura: tri niza ime[], poeni[], vreme[] čiji elementi sa istim indeksom „pripadaju“ istom takmičaru. Kod sortiranja bi trebalo zamenjivati sva tri niza – sa strukturom menjamo jedan element.

Strukture i funkcije, operator ->

Struktura se funkciji predaje po vrednosti – funkcija dobija kopiju i ne može da promeni original. Da bi funkcija menjala strukturu, prosleđuje joj se pokazivač, a polju se pristupa operatorom -> (p->x je isto što i (*p).x).

#include <stdio.h>

typedef struct {
    int x, y;
} Tacka;

/* po vrednosti: dobija kopiju, vraća rezultat */
int kvadratRastojanja(Tacka a, Tacka b) {
    int dx = a.x - b.x, dy = a.y - b.y;
    return dx * dx + dy * dy;
}

/* preko pokazivača: menja original */
void pomeri(Tacka *p, int dx, int dy) {
    p->x += dx;                  /* isto što i (*p).x += dx */
    p->y += dy;
}

int main(void) {
    Tacka a = {0, 0}, b = {3, 4};
    printf("d^2 = %d\n", kvadratRastojanja(a, b));
    pomeri(&a, 1, 1);
    printf("a = (%d, %d)\n", a.x, a.y);
    printf("d^2 = %d\n", kvadratRastojanja(a, b));
    return 0;
}

Za velike strukture i kada se ne menjaju, pokazivač se šalje i radi brzine – ne kopira se ceo sadržaj. Tada se piše const Tacka *p. Više o pokazivačima: Pokazivači u jeziku C.

Struktura u strukturi

Polje strukture može biti druga struktura:

#include <stdio.h>

typedef struct {
    int dan, mesec, godina;
} Datum;

typedef struct {
    char ime[30];
    Datum rodjen;                /* polje koje je i samo struktura */
} Osoba;

/* vraća negativan broj ako je d1 pre d2, 0 ako su jednaki, pozitivan inače */
int uporediDatume(Datum d1, Datum d2) {
    if (d1.godina != d2.godina) return d1.godina - d2.godina;
    if (d1.mesec != d2.mesec) return d1.mesec - d2.mesec;
    return d1.dan - d2.dan;
}

int main(void) {
    Osoba a = {"Mila", {12, 5, 2013}};
    Osoba b = {"Teodor", {3, 11, 2012}};
    Osoba *stariji = uporediDatume(a.rodjen, b.rodjen) < 0 ? &a : &b;
    printf("Stariji je %s, rodjen %d.%d.%d.\n", stariji->ime,
           stariji->rodjen.dan, stariji->rodjen.mesec, stariji->rodjen.godina);
    return 0;
}

Sortiranje niza struktura: qsort

Funkcija qsort iz stdlib.h sortira bilo kakav niz. Dobija niz, broj elemenata, veličinu jednog elementa i funkciju poređenja koja vraća negativan broj ako prvi element ide ispred drugog, nulu ako su jednaki i pozitivan broj inače.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char ime[30];
    int poeni;
} Takmicar;

/* više poena ide prvo; kod jednakih poena – abecedno */
int poredi(const void *a, const void *b) {
    const Takmicar *x = a, *y = b;
    if (x->poeni != y->poeni)
        return y->poeni - x->poeni;
    return strcmp(x->ime, y->ime);
}

int main(void) {
    int n;
    Takmicar t[100];
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
        scanf("%29s %d", t[i].ime, &t[i].poeni);

    qsort(t, n, sizeof(Takmicar), poredi);

    for (int i = 0; i < n; i++)
        printf("%d. %s %d\n", i + 1, t[i].ime, t[i].poeni);
    return 0;
}

U C++-u isti posao radi sort sa lambda funkcijom – vidi STL u C++-u. Unije (union) i raspored struktura u memoriji objašnjeni su u lekciji Uvod u strukture i unije.

Zadaci za vežbu

Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.

ZADATAK 01Težina: lako

Razlomci

Definisati strukturu Razlomak sa poljima br i im. Učitati dva razlomka (brojilac i imenilac, imenioci su pozitivni) i ispisati njihov zbir u skraćenom obliku br/im.

Primer 1

Ulaz

1 2 1 3

Izlaz

5/6

Primer 2

Ulaz

3 4 1 4

Izlaz

1/1
ZADATAK 02Težina: srednje

Odličan uspeh

Učitava se n, pa za svakog učenika ime i ocene iz 5 predmeta. Ispisati imena učenika sa prosekom bar 4,50, poređana po proseku opadajuće (kod istog proseka abecedno), uz prosek na dve decimale. Ako takvih nema, ispisati NEMA.

Primer

Ulaz

4
Marko 5 5 4 5 4
Ana 5 5 5 5 4
Iva 4 4 5 3 5
Luka 5 5 5 4 4

Izlaz

Ana 4.80
Luka 4.60
Marko 4.60
Česte greške i saveti
  • Posle zatvorene vitičaste zagrade definicije strukture ide tačka-zarez: struct Tacka { int x, y; };.
  • Bez typedef tip se zove struct Tacka, a ne samo Tacka.
  • Tačka (t.x) se koristi za promenljivu-strukturu, strelica (p->x) za pokazivač na strukturu.
  • Strukture se ne porede sa ==; napiši funkciju koja poredi polja.
  • U funkciji poređenja za qsort ne vraćaj razliku realnih brojeva (x->prosek - y->prosek) – rezultat se odseca na int i 0.3 postaje 0.

Proveri znanje

Otvori kviz

Strukture

1. Kako se pristupa polju ime promenljive u tipa struct Ucenik?




2. Ako je p pokazivač na strukturu, šta je p->x?




3. Šta funkcija dobija kada joj se struktura preda po vrednosti?




4. Šta treba da vrati funkcija poređenja za qsort kada prvi element ide ispred drugog?