Објашњени структурирани типови података у језику Ц.
У Ц постоје променљиве различитих типова података, као што су int
с, char
с и float
с. И омогућавају вам да складиштите податке.
Имамо и низове за груписање колекције података истог типа података.
Али у стварности нећемо увек имати луксуз да имамо податке само једне врсте. Ту се слика појављује у структури . У овом чланку ћемо сазнати више о структурираним типовима података у Ц.
Преглед садржаја
А. Основе
- Дефиниција и декларација
- Иницијализација и приступ члановима структуре
- Рад са променљивом структуре
- Низ структура
- Угњежђена структура
Б. Расподела меморије
- Поравнање података
- Подстава структуре
- Структура Поравнање чланова
- Структура паковања
Ц. Показивачи
- Показивач као члан
- Показивач на структуру
- Показивач и низ структура
Д. Функције
- Функција члана
- Структура као аргумент функције
- Структура као повратак функције
Е. Самореференцијалне структуре
Ф. Закључак
Кренимо, хоћемо ли?
Основе
1. Дефиниција и декларација
Структура је колекција једне или више променљивих, могуће различитих типова, груписаних под једним именом. То је кориснички дефинисани тип података.
Они помажу у организовању компликованих података у великим програмима, јер омогућавају да се група логички повезаних променљивих третира као једна.
На пример, ученик може имати својства имена, старости, пола и знакова. Могли бисмо да креирамо char
ацтер низ за name
, int
егер променљиву за roll
, char
ацтер променљиву за пол и int
егер низ за marks
.
Али ако има 20 или 100 ученика, биће тешко носити се са тим променљивим.
Можемо прогласити структуру користећи struct
кључну реч пратећи синтаксу као доле:
/* Syntax */ struct structureName { dataType memberVariable1; datatype memberVariable2; ... }; /* Example */ struct student { char name[20]; int roll; char gender; int marks[5]; };
Горња изјава дефинише нови тип података struct student
. Свака променљива ове врсте података ће се састојати од name[20]
, roll
, gender
и marks[5]
. Они су познати као чланови структуре.
Једном када је структура декларисана као нови тип података, тада се могу креирати променљиве тог типа података.
/* Variable declaration */ struct structureName structureVariable; /* Example /* struct student st1; struct student st2,st3,st4;
Свака променљива од struct student
има своје копије чланова.
Неколико важних груменова:
- Чланови структуре не заузимају меморију док се не креира променљива структуре.
- Можда сте приметили да користимо и
struct
као декларацију променљиве. Зар то није заморно?
Употребом typedef
кључне речи у декларацији структуре можемо спречити struct
поновно писање .
typedef struct students { char name[20]; int roll; char gender; int marks[5]; } STUDENT; /* or */ typedef struct { char name[20]; int roll; char gender; int marks[5]; } STUDENT; STUDENT st1,st2,st3,st4;
Према договору, велика слова се користе за дефиниције типова (као што су STUDENT
).
3. Дефиниција структуре и декларација променљиве могу се комбиновати на следећи начин.
struct student { char name[20]; int roll; chat gender; int marks[5]; }st1, st2, st3, st4;
4. Коришћење structureName
није обавезно. Доле наведени код је потпуно важећи.
struct { char name[20]; int roll; char gender; int marks[5]; }st1, st2, st3, st4;
5. Структуре су обично декларисане на врху датотеке изворног кода, чак и пре него што дефинишу функције (видећете зашто).
6. Ц не дозвољава иницијализацију променљивих унутар декларације структуре.
2. Иницијализација и приступ члановима структуре
Као и свака друга променљива, променљива структуре такође може бити иницијализована тамо где је декларисана. Између чланова и њихових иницијализирајућих вредности постоји однос један на један.
/* Variable Initialization */ struct structureName = { value1, value2,...}; /* Example */ typedef struct { char name[20]; int roll; char gender; int marks[5]; }STUDENT; void main(){ STUDENT st1 = { "Alex", 43, 'M', {76, 78, 56, 98, 92}}; STUDENT st2 = { "Max", 33, 'M', {87, 84, 82, 96, 78}}; }
Да бисмо приступили члановима, морамо да користимо .
( тачкасти оператор ).
/* Accessing Memebers of a Structure */ structureVariable.memberVariable /* Example */ printf("Name: %s\n", st1.name); printf("Roll: %d\n", st1.roll); printf("Gender: %c\n", st1.gender); for( int i = 0; i < 5; i++) printf("Marks in %dth subject: %d\n", i, st1.marks[i]); /* Output */ Name: Alex Roll: 43 Gender: M Marks in 0th subject: 76 Marks in 1th subject: 78 Marks in 2th subject: 56 Marks in 3th subject: 98 Marks in 4th subject: 92
Чланови се могу иницијализовати у декларацији променљиве у било ком редоследу .
.
STUDENT st3 = { .gender = 'M', .roll = 23, .name = "Gasly", .marks = { 99, 45, 67, 78, 94}};
Такође можемо иницијализовати првих неколико чланова, а преостале оставити празне. Међутим, неиницијализовани чланови би требали бити само на крају листе.
Неиницијализовани int
егери и бројеви са покретном зарезом имају подразумевану вредност 0
. То је \0
(НУЛЛ) за char
актере и жице.
STUDENT st4 = { "Kviyat", 65}; /* same as { "Kviyat", 65, '\0', { 0, 0, 0, 0, 0} } */
3. Рад са променљивом структуре
Like variables of primitive data types, we cannot perform arithmetic operations such as +
, -
, *
, /
, and so on. Likewise, relational and equality operators cannot be used with structure variables.
But, we can copy one structure variable to another, provided they belong to the same structure.
/* Invalid Operations */ st1 + st2 st1 - st2 st1 == st2 st1 != st2 etc. /* Valid Operation */ st1 = st2
We will have to compare the structure members individually to compare structure variables.
#include #include struct student { char name[20]; double roll; char gender; int marks[5]; }st1,st2; void main() { struct student st1= { "Alex", 43, 'M', {76, 78, 56, 98, 92}}; struct student st2 = { "Max", 33, 'M', {87, 84, 82, 96, 78}}; if( strcmp(st1.name,st2.name) == 0 && st1.roll == st2.roll) printf("Both are the records of the same student.\n"); else printf("Different records, different students.\n"); /* Copiying the structure variable */ st2 = st1; if( strcmp(st1.name,st2.name) == 0 && st1.roll == st2.roll) printf("\nBoth are the records of the same student.\n"); else printf("\nDifferent records, different students.\n"); } /* Output */ Different records, different students. Both are the records of the same student.
4. Array of a Structure
You have already seen how we had to create 4 different struct student
type variables to store the records of 4 students.
A better way would be to create an array of struct student
(just like an array of int
s).
struct student { char name[20]; double roll; char gender; int marks[5]; }; struct student stu[4];
To access the elements of the array stu
and the members of each element, we can use loops.
/* Taking values for the user */ for(int i = 0; i < 4; i++) { printf("Enter name:\n"); scanf("%s",&stu[i].name); printf("Enter roll:\n"); scanf("%d",&stu[i].roll); printf("Enter gender:\n"); scanf(" %c",&stu[i].gender); for( int j = 0; j < 5; j++) { printf("Enter marks of %dth subject:\n",j); scanf("%d",&stu[i].marks[j]); } printf("\n-------------------\n\n"); } /* Finding the average marks and printing it */ for(int i = 0; i < 4; i++) { float sum = 0; for( int j = 0; j < 5; j++) { sum += stu[i].marks[j]; } printf("Name: %s\nAverage Marks = %.2f\n\n", stu[i].name,sum/5); }
5. Nested Structure
Nesting a structure means having one or more structure variables inside another structure. Like we declare an int
member or char
member, we can also declare a structure variable as a member.
struct date { int date; int month; int year; }; struct student { char name[20]; int roll; char gender; int marks[5]; struct birth birthday; }; void main(){ struct student stu1;
The structure variable birthday
of type struct birth
is nested inside struct student
. It should be clear that you cannot nest a structure variable of type struct student
inside struct student
.
Note that the structure to be nested has to be declared first. Using .
, we can access the members contained within the inner structure as well as other members.
/* Example */ stu1.birthday.date stu1.birthday.month stu1.birthday.year stu1.name
Structure variables of different types can be nested as well.
struct birth { int date; int month; int year; }; struct relation { char fathersName[20]; char mothersName[20]; }; struct student { char name[20]; int roll; char gender; int marks[5]; struct birth birthday; struct relation parents; };
Memory Allocation
When a structure variable of some type is declared, structure members are allocated contiguous (adjacent) memory locations.
struct student { char name[20]; int roll; char gender; int marks[5]; } stu1;
Овде ће меморија бити додељена name[20]
, затим roll
, gender
и marks[5]
. То подразумева да ће величина st1
или struct student
бити сума величине његових чланова, зар не? Хајде да проверимо.
void main() { printf("Sum of the size of members = %I64d bytes\n", sizeof(stu1.name) + sizeof(stu1.roll) + sizeof(stu1.gender) + sizeof(stu1.marks)); printf("Using sizeof() operator = %I64d bytes\n",sizeof(stu1)); } /* Output */ Sum of the size of members = 45 bytes Using sizeof() operator = 48 bytes
Пошто се
sizeof()
оператер враћа
long long unsigned int
, користите га
%I64d
као спецификатор формата. Можда ћете морати да користите
%llu
или
%lld
у зависности од компајлера.
Коришћење %d
ће дати упозорење - формат '% д' очекује аргумент типа 'инт', али аргумент 2 има тип 'лонг лонг унсигнед инт'.
Коришћење sizeof()
оператора даје 3
више бајтова од збира величине чланова. Зашто? Где су та 3 бајта у меморији?
Let's answer the second question first. We can print the addresses of the members to find the addresses of those 3
bytes.
void main() { printf("Address of member name = %d\n", &stu1.name); printf("Address of member roll = %d\n", &stu1.roll); printf("Address of member gender = %d\n", &stu1.gender); printf("Address of member marks = %d\n", &stu1.marks); } /* Output */ Address of member name = 4225408 Address of member roll = 4225428 Address of member gender = 4225432 Address of member marks = 4225436
We can see that the array marks[5]
instead of being allocated from 4225433
has been allocated from 4224536
. But why?
1. Data Alignment
Before looking at data alignment, it is important to know how the processor reads data from the memory.
A processor reads one word in one cycle. This word is 4 bytes for a 32-bit processor and 8 bytes for a 64-bit processor. The lower the number of cycles, the better is the performance of the CPU.
One way to achieve this is by aligning the data. Aligning means that a variable of any primitive data type of size t
will always (by default) have an address that is a multiple of t
. This essentially is data alignment. This takes place every time.
Aligned addresses for some data types
Data types | Size (in bytes) | Address |
---|---|---|
char | 1 | multiple of 1 |
short | 2 | multiple of 2 |
int, float | 4 | multiple of 4 |
double, long, * (pointers) | 8 | multiple of 8 |
long double | 16 | multiple of 16 |
2. Structure Padding
You may need to insert some extra bytes between the members of the structure to align the data. These extra bytes are known as padding.
In our above example, the 3
bytes acted as padding. Without them, marks[0] which is of type int
(address multiple of 4) would have its base address as 4225433
(not a multiple of 4).
You can now probably see why structures can't be compared directly.
3. Structure Member Alignment
To explain this, we will take another example (you'll understand why).
struct example { int i1; double d1; char c1; } example1; void main() { printf("size = %I64d bytes\n",sizeof(example1)); }
What would be the output? Let's apply what we know.
i1
is of 4 bytes. It will be followed by a padding of 4 bytes because the address of d1
should be divisible by 8.
This will be followed by 8 and 1 byte respectively for d1
and c1
. Thus, the output should be 4 + 4 + 8 + 1 = 17 bytes.
/* Output */ size = 24 bytes
What? Wrong again! How? Through an array of struct example
, we can understand better. We will also print the address of the members of example2[0]
.
void main() { struct example example2[2]; printf("Address of example2[0].i1 = %d\n", &example2[0].i1); printf("Address of example2[0].d1 = %d\n", &example2[0].d1); printf("Address of example2[0].c1 = %d\n", &example2[0].c1); } /* Output */ Address of example2[0].i1 = 4225408 Address of example2[0].d1 = 4225416 Address of example2[0].c1 = 4225424
Let's suppose the size of example2[0]
is 17 bytes. This implies that the address of example2[1].i1
will be 4225425
. This isn't possible since the address of int
should be a multiple of 4.
Logically, the possible address for example2[1].i1
seems to be 4225428
, a multiple of 4.
This is wrong as well. Do you know why? The address of example2[1].d1
now will be ( 28 + 4 ( i1
) + 3 ( padding)) 4225436
which is not a multiple of 8.
In order to avoid such misalignment, the compiler introduces alignment to every structure. This is done by adding extra bytes after the last member, known as structure member alignment.
In the example discussed at the start of this section, this wasn't required (which is why we needed this other example).
A simple way to remember is through this rule: Address of structure and structure length must be multiples of t_max
. Here, t_max
is the maximum size taken by a member in the structure.
For struct example
, 8 bytes is the maximum size of d1
. Therefore, there is a padding of 7 bytes to the end of the structure, making its size 24 bytes.
Following these two rules, you can easily find the size of any structure:
- Any data type stores its value at an address that is a multiple of its size.
- Any structure takes the size which is a multiple of the maximum bytes taken by a member.
Though we are able to lower the CPU cycles, there is a significant amount of memory going to waste.
One way to decrease the amount of padding to a possible minimum is by declaring the member variables in decreasing order of their size.
If we follow this in struct example
, the size of the structure reduces to 16 bytes. The padding gets reduced from 7 to 3 bytes.
struct example { double d1; int i1; char c1; } example3; void main() { printf("size = %I64d bytes\n",sizeof(example3)); } /* Output */ size = 16 bytes
4. Structure Packing
Packing is the opposite of padding. It prevents the compiler from padding and removes the unallocated memory.
In the case of Windows, we use the #pragma pack
directive, which specifies the packing alignment for structure members.
#pragma pack(1) struct example { double d1; int i1; char c1; } example4; void main() { printf("size = %I64d bytes\n",sizeof(example4)); } /* Output */ size = 13 bytes
Ово осигурава поравнање чланова на граници од 1 бајта. Другим речима, адреса било ког типа података мора бити вишекратник од 1 бајта или њихове величине (шта год је ниже).
Поинтерс
Ако желите да очистите показиваче пре него што кренете напред, ево везе до чланка који детаљно покрива показиваче.1. Показивач као члан
Структура такође може имати показиваче као чланове.
struct student { char *name; int *roll; char gender; int marks[5]; }; void main() { int alexRoll = 44; struct student stu1 = { "Alex", &alexRoll, 'M', { 76, 78, 56, 98, 92 }}; }
Помоћу .
(оператора тачака) можемо поново приступити члановима. Будући да roll
сада има адресу alexRoll
, мораћемо да одузмемо референцу stu1.roll
да бисмо добили вредност (и не stu1.(*roll)
).
printf("Name: %s\n", stu1.name); printf("Roll: %d\n", *(stu1.roll)); printf("Gender: %c\n", stu1.gender); for( int i = 0; i < 5; i++) printf("Marks in %dth subject: %d\n", i, stu1.marks[i]); /* Output */ Name: Alex Roll: 43 Gender: M Marks in 0th subject: 76 Marks in 1th subject: 78 Marks in 2th subject: 56 Marks in 3th subject: 98 Marks in 4th subject: 92
2. Показивач на структуру
Попут целобројних показивача, показивача на низ и показивача на функције, и ми имамо показивач на структуре или структурне показиваче .
struct student { char name[20]; int roll; char gender; int marks[5]; }; struct student stu1 = {"Alex", 43, 'M', {76, 98, 68, 87, 93}}; struct student *ptrStu1 = &stu1;
Here, we have declared a pointer ptrStu1
of type struct student
. We have assigned the address of stu1
to ptrStu1
.
ptrStu1
stores the base address of stu1
, which is the base address of the first member of the structure. Incrementing by 1 would increase the address by sizeof(stu1)
bytes.
printf("Address of structure = %d\n", ptrStu1); printf("Adress of member `name` = %d\n", &stu1.name); printf("Increment by 1 results in %d\n", ptrStu1 + 1); /* Output */ Address of structure = 6421968 Adress of member 'name' = 6421968 Increment by 1 results in 6422016
We can access the members of stu1
using ptrStu1
in two ways. Using *
(indirection operator) or using ->
(infix or arrow operator).
With *
, we will continue to use the .
(dot operator) whereas with ->
we won't need the dot operator.
printf("Name w.o using ptrStu1 : %s\n", stu1.name); printf("Name using ptrStu1 and * : %s\n", (*ptrStu1).name); printf("Name using ptrStu1 and -> : %s\n", ptrStu1->name); /* Output */ Name without using ptrStu1: Alex Name using ptrStu1 and *: Alex Name using ptrStu1 and ->: Alex
Similarly, we can access and modify other members as well. Note that the brackets are necessary while using *
since the dot operator(.
) has higher precedence over *
.
3. Pointer and Array of Structure
We can create an array of type struct student
and use a pointer to access the elements and their members.
struct student stu[10]; /* Pointer to the first element (structure) of the array */ struct student *ptrStu_type1 = stu; /* Pointer to an array of 10 struct student */ struct student (*ptrStu_type2)[10] = &stu;
Note that ptrStu_type1
is a pointer to stu[0]
whereas ptrStu_type2
is a pointer to the whole array of 10 struct student
. Adding 1 to ptrStu_type1
would point to stu[1]
.
We can use ptrStu_type1
with a loop to traverse through the elements and their members.
for( int i = 0; i name, ( ptrStu_type1 + i)->roll);
Functions
1. Function as a Member
Functions can not be a member of a structure. However, using function pointers, we can call functions using .
. Just keep in mind that this is not recommended.
struct example { int i; void (*ptrMessage)(int i); }; void message(int); void message(int i) { printf("Hello, I'm a member of a structure. This structure also has an integer with value %d", i); } void main() { struct example eg1 = {6, message}; eg1.ptrMessage(eg1.i); }
We have declared two members, an int
eger i
and a function pointer ptrMessage
inside struct example
. The function pointer points to a function that takes an int
eger and returns void
.
message
is one such function. We initialized eg1
with 6
and message
. Then we use .
to call the function using ptrMessage
and pass eg1.i
.
2. Structure as a Function Argument
Like variables, we can pass individual structure members as arguments.
#include struct student { char name[20]; int roll; char gender; int marks[5]; }; void display(char a[], int b, char c, int marks[]) { printf("Name: %s\n", a); printf("Roll: %d\n", b); printf("Gender: %c\n", c); for(int i = 0; i < 5; i++) printf("Marks in %dth subject: %d\n",i,marks[i]); } void main() { struct student stu1 = {"Alex", 43, 'M', {76, 98, 68, 87, 93}}; display(stu1.name, stu1.roll, stu1.gender, stu1.marks); } /* Output */ Name: Alex Roll: 43 Gender: M Marks in 0th subject: 76 Marks in 1th subject: 98 Marks in 2th subject: 68 Marks in 3th subject: 87 Marks in 4th subject: 93
Note that the structure struct student
is declared outside main()
, at the very top. This is to ensure that it is available globally and display()
can use it.
If the structure is defined inside main()
, its scope will be limited to main()
.
Passing structure members is not efficient when there are a large number of them. Then structure variables can be passed to a function.
void display(struct student a) { printf("Name: %s\n", a.name); printf("Roll: %d\n", a.roll); printf("Gender: %c\n", a.gender); for(int i = 0; i < 5; i++) printf("Marks in %dth subject: %d\n",i,a.marks[i]); } void main() { struct student stu1 = {"Alex", 43, 'M', {76, 98, 68, 87, 93}}; display(stu1); }
If the size of the structure is large, then passing a copy of it won't be very efficient. We could pass a structure pointer to a function. In this case, the address of the structure is passed as an actual argument.
void display(struct student *p) { printf("Name: %s\n", p->name); printf("Roll: %d\n", p->roll); printf("Gender: %c\n", p->gender); for(int i = 0; i marks[i]); } void main() { struct student stu1 = {"Alex", 43, 'M', {76, 98, 68, 87, 93}}; struct student *ptrStu1 = &stu1; display(ptrStu1); }
Passing an array of structure to a function is similar to passing an array of any type to a function. The name of the array, which is the base address of the array of the structure, is passed to the function.
void display(struct student *p) { for( int j = 0; j name); printf("Roll: %d\n", (p+j)->roll); printf("Gender: %c\n", (p+j)->gender); for(int i = 0; i marks[i]); } } void main() { struct student stu1[10]; display(stu1); }
3. Structure as a Function Return
We can return a structure variable, just like any other variable.
#include struct student { char name[20]; int roll; char gender; int marks[5]; }; struct student increaseBy5(struct student p) { for( int i =0; i < 5; i++) if(p.marks[i] + 5 <= 100) { p.marks[i]+=5; } return p; } void main() { struct student stu1 = {"Alex", 43, 'M', {76, 98, 68, 87, 93}}; stu1 = increaseBy5(stu1); printf("Name: %s\n", stu1.name); printf("Roll: %d\n", stu1.roll); printf("Gender: %c\n", stu1.gender); for(int i = 0; i < 5; i++) printf("Marks in %dth subject: %d\n",i,stu1.marks[i]); } /* Output */ Name: Alex Roll: 43 Gender: M Marks in 0th subject: 81 Marks in 1th subject: 98 Marks in 2th subject: 73 Marks in 3th subject: 92 Marks in 4th subject: 98
The function increaseBy5()
increases the marks by 5 for subjects where, after increasing the marks, it is less than or equal to 100. Note that the return type is a structure variable of type struct student
.
While returning a structure member the return type has to be that of the member.
A structure pointer can also be returned by a function.
#include #include struct rectangle { int length; int breadth; }; struct rectangle* function(int length, int breadth) { struct rectangle *p = (struct rectangle *)malloc(sizeof(struct rectangle)); p->length = length; p->breadth = breadth; return p; } void main() { struct rectangle *rectangle1 = function(5,4); printf("Length of rectangle = %d units\n", rectangle1->length); printf("Breadth of rectangle = %d units\n", rectangle1->breadth); printf("Area of rectangle = %d square units\n", rectangle1->length * rectangle1->breadth); } /* Output */ Length of rectangle = 5 units Breadth of rectangle = 4 units Area of rectangle = 20 square units
Notice we have allocated the memory of size struct rectangle
dynamically using malloc()
. Since it returns a void pointer, we have to typecast it to a struct rectangle
pointer.
Self-Referential Structure
We discussed that pointers can be a member of a structure too. What if the pointer is a structure pointer? The structure pointer can either be of the same type as the structure or different.
Self-referential structures are those which have structure pointer(s) of the same type as their member(s).
struct student { char name[20]; int roll; char gender; int marks[5]; struct student *next; };
This is a self-referential structure where next
is a struct student
type structure pointer.
We will now create two structure variables stu1
and stu2
and initialize them with values. We will then store the address of stu2
in next
member of stu1
.
void main() { struct student stu1 = {"Alex", 43, 'M', {76, 98, 68, 87, 93}, NULL}; struct student stu2 = { "Max", 33, 'M', {87, 84, 82, 96, 78}, NULL}; stu1.next = &stu2; }
We can now access the members of stu2
using stu1
and next
.
void main() { printf("Name: %s\n", stu1.next->name); printf("Roll: %d\n", stu1.next->roll); printf("Gender: %c\n", stu1.next->gender); for(int i = 0; i marks[i]); } /* Output */ Name: Max Roll: 33 Gender: M Marks in 0th subject: 87 Marks in 1th subject: 84 Marks in 2th subject: 82 Marks in 3th subject: 96 Marks in 4th subject: 78
Suppose we want a different structure variable after stu1
, that is insert another structure variable between stu1
and stu2
. This can be done easily.
void main() { struct student stuBetween = { "Gasly", 23, 'M', {83, 64, 88, 79, 91}, NULL}; st1.next = &stuBetween; stuBetween.next = &stu2; }
Now stu1.next
stores the address of stuBetween
. And stuBetween.next
has the address of stu2
. We can now access all three structures using stu1
.
printf("Roll Of %s: %d\n", stu1.next->name, stu1.next->roll); printf("Gender Of %s: %c\n", stu1.next->next->name, stu1.next->next->gender); /* Output */ Roll Of Gasly: 23 Gender Of Max: M
Notice how we have formed a link between stu1
, stuBetween
and stu3
. What we have discussed here is the starting point of a Linked List.
Самореференцијалне структуре су врло корисне у стварању структура података као што су повезана листа , стекови , редови , графикони и тако даље.
Закључак
Готово! Покрили смо све, од дефиниције структуре до употребе аутореференцијалних структура.
Покушајте да резимирате све поттеме које сте прочитали. Ако их се можете сетити, браво! Прочитајте поново оне којих се не сећате.
Следећи логични корак био би сазнати више о повезаним листама и разним другим структурама података које су овде коришћене.
Наставити са учењем. Останите код куће и будите сигурни.