Структуре

У претходним лекцијама је објашњено како можемо направити колекције података истог типа помоћу једнодимензионалних и вишедимензионалних низова и стрингова. На пример:

int ocene[5] = {1, 2, 3, 4, 5};
char ime[20] = "Velimir Radlovacki";
double matrica[3][3] = {{1.0, 1.1, 1.2},
                        {2.1, 1.5, 3.3},
                        {8.5, 1.5, 1.7}};

Међутим, у пракси нам је често потребно да направимо колекције података различитих типова. На пример, за евиденцију ученика у одељењу потребна нам је следећа колекција података: редни број ученика, презиме и име ученика и просек.

int redbr;
char prezime[20];
char ime[20];
float prosek;

Овакву колекцију података није могуће сачувати у низу, јер је низ колекција података истог типа. Јасно је да је неопходно креирати сложени тип података који може чувати елементе различитих типова.

Структура је сложени тип података која се састоји од одређеног броја елемената који могу бити различитих типова. У другим програмским језицима уместо назива “структура” могу се наћи и називи “запис” или “слог”. Елементи структурe називају се чланови структуре и обележавају се идентификаторима. Чланови структуре могу бити било којег основног или сложеног типа података, што значи да чланови структуре могу бити и друге структуре, низови, стрингови.

Структура је сложени тип података којег дефинише програмер. Ошти облик дефиниције структуре, односно синтакса, изгледа овако:

struct ime-strukture { lista-deklaracija } ;

Кључна реч struct је настала као скраћено име за структурирани тип података. ime-strukture је идентификатор за идентификацију структуре која се дефинише. Име структуре нема статус идентификатора типа података, па ако постоје позивања на ову структуру, увек мора да се наведе struct ime-strukture – само навођење имена структуре није довољно. lista-deklaracija представља набрајања чланова структуре, где свака декларација има општи облик за дефинисање података којег смо и до сада користили. На пример,

struct ucenik {
    int redbr;
    char prezime[20];
    char ime[20];
    float prosek;
};

Структуре се обично дефинишу на почетку програма, пре функције main() и осталих функција, како би се могле у њима користити.

 

Како је свака структура посебан тип података, то значи да се могу декларисати и иницијализовати променљиве типа структуре. На пример, следећим кодом:

struct ucenik u1;

декларисали смо нову променљиву u1 типа структуре ucenik, или:

struct ucenik u2 = {2, "Jovanovic", "Petar", 4.67};

декларисали смо и иницијализовали нову променљиву u2 типа структуре ucenik.

Променљиве типа структуре могуће је декларисати и одмах након декларације структуре. У следећем примеру:

struct ucenik {
    int redbr;
    char prezime[20];
    char ime[20];
    float prosek;
}u3;

декларисали смо структуру ucenik и променљиву u3 типа структуре ucenik.

 

Појединачним члановима структуре приступа се коришћењем оператора тачка на следећи начин: ime-promenljive.ime-člana. Када приступимо једном члану структуре, онда можемо прочитати, доделити или променити његову вредност. На пример:

u4.redbr = 15;
u4.prezime = "Petrovic"; 
gets(u4.ime);
scanf("%f", &u4.prosek);
printf("%d %s %s %f", u4.redbr, u4.prezime, u4.ime, u4.prosek);

 

Постоји још један начин иницијализације чланова структуре, такозваним означеним иницијализаторима (designated initializers) који је користан када желимо да иницијализујемо само одређени број чланова структуре.

struct ucenik u5 = {.redbr = 5, .prosek = 3.67};

У примеру изнад иницијализовали смо само два члана структуре.

 

ЗАДАТАК 1. Напишите програм у програмском језику C за евиденцију података о 3 ученика користећи структуру ucenik чији су чланови int redbr, char prezime[20], char ime[20] и float prosek. Подаци о ученицима требају бити унети у самом кôду програма приликом иницијализације.

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

struct ucenik {
    int redbr;
    char prezime[20];
    char ime[20];
    float prosek;
};

int main(void) {
    struct ucenik u1 = { 4, "Milanovic", "Ana", 4.50 };
    struct ucenik u2 = { 9, "Jovanovic", "Petar", 4.67 };
    struct ucenik u3 = { 23, "Petrovic", "Jovan", 4.00 };

    printf("%d %s %s %.2f\n", u1.redbr, u1.prezime, u1.ime, u1.prosek);
    printf("%d %s %s %.2f\n", u2.redbr, u2.prezime, u2.ime, u2.prosek);
    printf("%d %s %s %.2f", u3.redbr, u3.prezime, u3.ime, u3.prosek);

    return 0;
}

 

ЗАДАТАК 2. Преправите претходни задатак тако да се подаци о ученицима уносе са стандардног улаза (тастатуре).

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

struct ucenik {
    int redbr;
    char prezime[20];
    char ime[20];
    float prosek;
};

int main(void) {
    struct ucenik u1, u2, u3;
    
    printf("Unesi red.br. 1. ucenika: ");
    scanf("%d", &u1.redbr);
    printf("Unesi prezime 1. ucenika: ");
    scanf("%s", &u1.prezime);
    printf("Unesi ime 1. ucenika: ");
    scanf("%s", &u1.ime);
    printf("Unesi prosek 1. ucenika: ");
    scanf("%f", &u1.prosek);

    printf("Unesi red.br. 2. ucenika: ");
    scanf("%d", &u2.redbr);
    printf("Unesi prezime 2. ucenika: ");
    scanf("%s", &u2.prezime);
    printf("Unesi ime 2. ucenika: ");
    scanf("%s", &u2.ime);
    printf("Unesi prosek 2. ucenika: ");
    scanf("%f", &u2.prosek);

    printf("Unesi red.br. 3. ucenika: ");
    scanf("%d", &u3.redbr);
    printf("Unesi prezime 3. ucenika: ");
    scanf("%s", &u3.prezime);
    printf("Unesi ime 3. ucenika: ");
    scanf("%s", &u3.ime);
    printf("Unesi prosek 3. ucenika: ");
    scanf("%f", &u3.prosek);

    printf("%d %s %s %.2f\n", u1.redbr, u1.prezime, u1.ime, u1.prosek);
    printf("%d %s %s %.2f\n", u2.redbr, u2.prezime, u2.ime, u2.prosek);
    printf("%d %s %s %.2f", u3.redbr, u3.prezime, u3.ime, u3.prosek);

    return 0;
}

Можете приметити да је кôд програма за евиденцију података за само 3 ученика прилично дугачак, да се и приликом уноса и приликом исписа података одређене ствари понављају, као и да би било елегантније да се овај задатак реши помоћу низа чији су елементи структуре.

 

Низ структура

У случајевима када нам је потребно више променљивих типа одређене структуре, није практично да их декларишемо појединачно, што се види из претходног примера. Функционалније решење је да декларишемо низ кога чине елементи који су типа структуре.

Низ структура декларишемо исто као и било који други низ. На пример, ако је декларисана структура ucenik:

struct ucenik {
    int redbr;
    char prezime[20];
    char ime[20];
    float prosek;
};

а ми желимо да декларишемо низ од 30 елемената типа структуре ucenik, то би урадили на следећи начин:

struct ucenik ucenici[30];

или би низ декларисали одмах након декларације структуре:

struct ucenik {
    int redbr;
    char prezime[20];
    char ime[20];
    float prosek;
}ucenici[30];

Као и код низова које смо до сада користили, сваки елемент у низу има тачно одређену позицију на основу које му се додељује јединствени индекс. Вредност индекса првог елемента је 0, другог 1, трећег 2 итд. што значи да је индекс низа типа int.

 

ЗАДАТАК 3. Преправите ЗАДАТАК 2. тако да се подаци о ученицима уносе са стандардног улаза и чувају у низу чији су елементи типа структуре.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

struct ucenik {
    int redbr;
    char prezime[20];
    char ime[20];
    float prosek;
}ucenici[3];

int main(void) {
    int i;

    for (i = 0; i < 3; i++) {
        printf("Unesi red.br. %d. ucenika: ", i + 1);
        scanf("%d", &ucenici[i].redbr);
        printf("Unesi prezime %d. ucenika: ", i + 1);
        scanf("%s", &ucenici[i].prezime);
        printf("Unesi ime %d. ucenika: ", i + 1);
        scanf("%s", &ucenici[i].ime);
        printf("Unesi prosek %d. ucenika: ", i + 1);
        scanf("%f", &ucenici[i].prosek);
    }

    for (i = 0; i < 3; i++) {
        printf("%d %s %s %.2f\n", ucenici[i].redbr, ucenici[i].prezime,
        ucenici[i].ime, ucenici[i].prosek);
    }
    
    return 0;
}

Задатак је сада много елегантније решен, а кôд програма можемо лако преправити за евиденцију података о било ком броју ученика променом броја елемената низа.

 

Дефиниција типа података структуре

Структуре се често користе заједно са ознаком за дефиницију типа typedef што значи да се уводи нови тип података структуре.

typedef struct ime-strukture { lista-deklaracija } ime-tipa;

Идентификатор ime-tipa може касније да се користи као идентификатор типа без навођења службене речи struct. Идентификатор за идентификацију структуре ime-strukture којег смо користили приликом декларације структуре се обично изоставља.

У следећем примеру:

typedef struct {
    int redbr;
    char prezime[20];
    char ime[20];
    float prosek;
}ucenik;
...
ucenik u1, u2;

увели смо нови тип података структуре ucenik и дефинисали две променљиве тог типа u1 и u2.

 

ЗАДАТАК 4. Како би јасно видели разлику између коришћења структуре и коришћења типа података структуре, напишите програм у којем је дефинисана структура ucenik и тип података структуре nastavnik, па су потом додељене исписане вредности за једног ученика и једног наставника.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

struct ucenik {
    int redbr;
    char prezime[20];
    char ime[20];
};

typedef struct {
    char prezime[20];
    char ime[20];
    char predmet[80];
}nastavnik;

int main(void) {   
    struct ucenik u1;
    nastavnik n1;

    u1.redbr = 1;
    strcpy(u1.prezime, "Peric");
    strcpy(u1.ime, "Pera");
    puts("Ucenik:");
    printf("Redni broj: %d\n", u1.redbr);
    printf("Prezime: %s\n", u1.prezime);
    printf("Ime: %s\n\n", u1.ime);
    
    strcpy(n1.prezime, "Radlovacki");
    strcpy(n1.ime, "Velimir");
    strcpy(n1.predmet, "Programiranje");
    puts("Nastavnik:");
    printf("Prezime: %s\n", n1.prezime);
    printf("Ime: %s\n", n1.ime);
    printf("Ime: %s\n", n1.predmet);

    return 0;
}

 

Угнежђене структуре

Структура може бити члан структуре (структура у структури, угнежђена структура). Члановима угнежђене структуре приступамо користећи операторе тачка, наводећи прво променљиве типа спољашње, па променљиве типа унутрашње (угнежђене) структуре, па име члана.

ЗАДАТАК 5. Напишите програм у коме је дефинисана структура kontakt унутар структуре ucenik. Доделите вредности члановима структуре, па их потом испишите на стандардни излаз.

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>

struct kontakt {
    char ulicaiBroj[80];
    char mesto[30];
    int pttBroj;
    char telefon[20];
};

struct ucenik {
    int redbr;
    char prezime[20];
    char ime[20];
    struct kontakt k1;
};

int main(void) {
    struct ucenik u1 = { 7, "Peric", "Pera", "Vrsacka 1", "Vrsac", 26300,
        "+381 63 834 5678" };
    printf("Redni broj: %d\n", u1.redbr);
    printf("Prezime i ime: %s %s\n", u1.prezime, u1.ime);
    printf("Ulica i broj: %s\n", u1.k1.ulicaiBroj);
    printf("Mesto i PTT: %s %d\n", u1.k1.mesto, u1.k1.pttBroj);
    printf("Telefon: %s\n", u1.k1.telefon);
    return 0;
}

 

Структуре и функције

Структуре се могу прослеђивати као аргументи функцијa и такође, структура може бити вредност коју враћа функција!

ЗАДАТАК 6. Напишите програм у коме је дефинисан тип структуре ucenik и две функције – прва функција kreirajUcenika као аргументе узима појединачне вредности чланова структуре и враћа вредност типа структуре, а друга stampajUcenika као параметар добија структуру и исписује вредности свих њених чланова (не враћа ништа).

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>

typedef struct {
    int redbr;
    char prezime[20];
    char ime[20];
}ucenik;

ucenik kreirajUcenika(int redbr, char prezime[20], char ime[20]) {
    ucenik u;
    u.redbr = redbr;
    strcpy(u.prezime, prezime);
    strcpy(u.ime, ime);
    return u;
}

void stampajUcenika(ucenik u) {
    printf("Redni broj: %d\n", u.redbr);
    printf("Prezime: %s\n", u.prezime);
    printf("Ime: %s\n", u.ime);
};

int main(void) {
    ucenik u1, u2;
    
    u1.redbr = 12;
    strcpy(u1.prezime, "Peric");
    strcpy(u1.ime, "Pera"); 
    puts("Ucenik 1:");
    stampajUcenika(u1);
    
    u2 = kreirajUcenika(13, "Anic", "Ana");
    puts("Ucenik 2:");
    stampajUcenika(u2);
    
    return 0;
}

 

Показивачи на структуре

Показиваче на структуре дефинишемо као и показиваче на податке било ког другог типа. На пример, ако имамо дефинисан тип података структуре ucenik, можемо дефинисати показивач pUcenik на тип структуре ucenik:

typedef struct {
    int redbr;
    char prezime[20];
    char ime[20];
}ucenik;
...
ucenik *pUcenik;

Показивачу на структуру можемо доделити адресу променљиве типа структуре. На пример:

typedef struct {
    int redbr;
    char prezime[20];
    char ime[20];
}ucenik;
...
ucenik *pUcenik, u1;
pUcenik = &u1;

Члановима у структури можемо приступити помоћу показивача коришћењем оператора ->. На пример:

typedef struct {
    int redbr;
    char prezime[20];
    char ime[20];
}ucenik;
...
ucenik *pUcenik, u1;
u1.redbr = 15;
strcpy(u1.ime, "Pera");
strcpy(u1.prezime, "Peric");
pUcenik = &u1;
printf("%s %s %d", pUcenik->redbr, pUcenik->prezime, pUcenik->ime);

Уместо коришћења оператора -> као што је приказано у претходном примеру:

pUcenik->ime

члановима структуре можемо приступити и на следећи начин:

(*pUcenik).ime

са напоменом да су заграде обавезне због приоритета оператора, као и да се овај начин ређе користи.

 

Показивачи на низ структура

Већ смо у претходним примерима дефинисали низ структура и додељивали вредности његовим елементима, на пример:

typedef struct {
    int redbr;
    char prezime[20];
    char ime[20];
}ucenik;
...
ucenik ucenici[30];
ucenici[0].redbr = 1;
strcpy(ucenici[0].prezime, "Aleksic");
strcpy(ucenici[0].ime, "Aleksa");

Елементима низа структура можемо доделити вредности и помоћу показивача. На пример, доделимо вредности следећем елементу претходно дефинисаног низа:

(ucenici+1)->redbr = 2;
strcpy((ucenici+1)->prezime, "Boric");
strcpy((ucenici+1)->ime, "Bora");