МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
Національний університет “Львівська політехніка”
СТРУКТУРИ ТА ОБ’ЄДНАННЯ В МОВІ ПРОГРАМУВАННЯ С
Інструкція
до лабораторної роботи № 10
з курсу “Проблемно-орієнтовані мови програмування”
для студентів базового напрямку 6.08.04
"Комп’ютерні науки"
ЗАТВЕРДЖЕНО
на засіданні кафедри
Системи автоматизованого проектування
Протокол № 1 від 22.08.2011 р.
ЛЬВІВ 2011
1. МЕТА РОБОТИ
Мета роботи - навчитися використовувати структури та об’єднання для програмування на мові С.
2. ТЕОРЕТИЧНІ ВІДОМОСТІ
Структури
Структура - це набір з однієї або більше змінних, можливо різних типів, згрупованих під одним ім'ям для зручності обробки. (У деяких мовах, наприклад Паскаль, структури називаються “записами”).
Розглянемо приклад. Уявіть собі, що на календарі сьогодні 1 січня 2009 року. Дата складається з складових таких, як день, рік та назва місяця. Ці три змінні можна об’єднати в одну структуру вигляду:
struct DATE {
int day;
int year;
char mon_name[4];
};
Опис структури, що складається з взятого в фігурні дужки списку полів, починається з ключового слова struct. Після слова struct пишеться ім'я, так звана Мітка структури (тут це DATE). Ім’я може бути відсутнє. Така мітка іменує структури цього виду і може використовуватися надалі як скорочений запис детального опису. Елементи або змінні, згадані в структурі, називаються членами (полями, компонентами). Мітки і члени структур можуть мати такі ж імена, що і звичайні змінні (тобто ті, що не є членами структур), оскільки їх імена завжди можна розрізнити по контексту. Звичайно однакові імена присвоюють тільки тісно пов'язаним об'єктам.
Точно так само, як у разі будь-якого іншого базового типу, за правою фігурною дужкою, що закриває список членів, може записуватись список змінних.
Оператор
struct { } x, y, z; // описує struct { }
синтаксично аналогічний
int x, y, z; // описує int
в тому сенсі, що кожен з операторів описує x, y та z в якості змінних відповідних типів і приводить до виділення для них пам’яті.
Опис структури, за яким не має списку змінних, не приводить до виділення пам'яті (немає змінної); він тільки визначає шаблон або форму структури. Але, якщо в такому опису є мітка, то ця мітка може бути використана пізніше при визначенні фактичних примірників структур. Наприклад, якщо дано приведений вище опис Date, то struct DATE d; визначає змінну d як структура типу Date.
Операція вказівника на член структури "." зв'язує ім'я структури та ім'я члена. Наприклад:
d.day = 1;
d.year = 2000;
d.mon_name = “січ”;
В програмі це може бути реалізовано наступним чином.
#include <stdio.h>
#include <string.h> // для strcpy()
struct DATE {
int day;
int year;
char mon_name[4];
};
int main(void)
{
struct DATE d;
// заповнимо структуру датою 1 січня 2000 року
d.day = 1;
d.year = 2000;
strcpy(d.mon_name, “січ”);
// виведемо на екран монітора цю дату
printf(“\nDate - %d %s %d\n”, d.day, d.mon_name, d.year);
return 0;
}
Результатом виконання програми буде напис : Date - 1 січ 2000.
Структури можуть бути вкладеними. Для прикладу наведемо структуру STUDENT:
struct STUDENT {
char name[25];
char address[40];
long int zipcode; // поштовий индекс
long int st_number; // номер студ. квитка
double scholarship; // стипендія
int course; // курс
char group[7]; // група
struct DATE birthdate; // дата народження
struct DATE heredate; // дата вступу в університет
};
Структура STUDENT містить дві структури типа DATE . Якщо ми визначимо person як struct STUDENT person, то запис person.birthdate.year буде зсилатися на рік народження студента.
Над змінною структури можна виконати операції взяття адреси змінної та присвоєння її вказівнику.
struct DATE d1, *d2; //оголошено змінну типу DATE d1,та вказівник на тип DATE d2
d2 = &d1; // &d1 - взяття адреси змінної d1 та присвоєння його змінній d2
Для демонстрації прикладу використання цих операцій над структурами перепишемо наведену вище програму так, щоб ввід даних про дату відбувався в фунrції input_date(), а вивід в процедурі print_date(). В такому випадку наша програма набире наступного вигляду:
#include <stdio.h>
#include <string.h> // для strcpy()
struct DATE {
int day;
int year;
char mon_name[4];
};
struct DATE * input_date(int day, int year, char *month)
{
struct DATE tmp;
tmp.day = day;
tmp.year = year;
strcpy(tmp.mon_name, month); // копіювання стрічки
return &tmp; // взяття адресу змінної
}
void print_date(struct DATE *d)
{
printf(“\nDate - %d %s %d\n”, d->day, d->mon_name, d->year);
}
int main(void)
{
struct DATE d;
d = *(input_date(1, 2000,”січ”));
print_date(&d);
return 0;
}
// або елегантніше
int main(void)
{
print_date(input_date(1, 2000,”січ”));
return 0;
}
Розглянемо детальніше функцію void print_date(struct DATE * d). Як видно з оголошення функції, як формальний параметр використано вказівник на структуру. В такому випадку для звертання до полів структури використовується оператор “->”. Для наочної різниці між звертанням змінної типу DATE та вказівником на змінну типу DATE наведено таблицю 1.
Таблиця 1.
struct DATE d;
struct DATE d,*d1;
d1 = &d;
d.date = 1;
d.year = 2000;
strcpy(d.mon_name, “січ”);
d1->date = 1;
d1->year = 2000;
strcpy(d1->mon_name, “січ”);
Структури можна об'єднувати в масив. Тоді запис struct DATE d[5]; відображає масив з п’яти елементів типу DATE. Доступ до полів структури відбувається наступним чином:
d[0].year;
d[0].date;
Розмір структури можна взнати використавши стандартну операцію. Справа в тому що в мові "C" передбачена унарна операція sizeof, яка виконується під час компіляції, дозволяючи обчислити розмір будь-якого об’єкта. Вираз
sizeof (object)
видає ціле число, що дорівнює розміру вказаного об’єкта. (Розмір визначається в неспецифіцикованних одиницях "байти", які мають той же розмір, що і змінні типу char). Об’єкт може бути фактичною змінною, масивом і структурою, або іменем основного типу, як int або double, або іменем похідного типу, як структура. В нашому випадку
int size;
struct DATE d;
size = sizeof(d);
Структури широко використовуються при побудові різноманітних дерев та списків.
Доступ до компонентів структури
Доступ до полів структури здійснюється за допомогою оператора "." при безпосередній роботі зі структурою або "->" - при використанні вказівників на структуру. Ці оператори (. і ->) називаються селекторами членів класу. Загальний синтаксис для доступу до компонентів структури наступний:
ім'я_змінної_структури . член_даних ;
ім'я_вказівника -> ім'я_поля;
(*ім'я_вказівника) . ім'я_поля;
Наприклад:
1. Прямий доступ до елемента (оператор " . "):
datel[5].day=10;
datel[5].year=1983;
strcpy (bguіr [10].fam, "Ільків");
strcpy (bguіr [10].іm, "Сергій");
bgulr[10].weіght=70.0;
bguіrtl] .heіght=180;
bguіr[і].bіrthday.day=15;
bguіr[10].bіrthday.month=l;
bguіr[10].bіrthday.year=1980;
/* Використовуючи пряме звертання до елемента, присвоюємо значення вибраній змінній. Текст поміщається в змінну, використовуючи функцію копіювання - strcpy(); */
2. Доступ по вказівнику (оператор "->"). Попередні оператори можна реалізувати в такий спосіб:
strcpy((per_ptr+10)->fam, "Ільків");
strcpy((per_ptr+l0)->іm,"Сергій");
(per_ptr+10)->weіght=70.0;
(per_ptr+10)->heіght=180;
(per_ptr+10)->bіrthday.day=15;
(per_ptr+10)->bіrthday.month=l;
(per_ptr+10)->bіrthday.year=1980;
/* Використовуючи доступ по вказівнику на структуру, присвоюємо значення відповідній змінній. Вказівник можна використовувати й так:
strcpy(*(per_ptr+10)).fam,"Ільків");
strcpy((*(per_ptr+10)).іm,"Сергій");
(*(per_ptr+10>).weіght=70.0;
(*(per_ptr+10)).heіght=180;
(*(per_ptr+10)).bіrthday.day=15;
(*(per_ptr+10)).bіrthday.month=l;
(*(per_ptr+10)).bіrthday.year=1980; */
Слід зазначити, що якщо змінна i задає номер елемента в масиві структур, то наступні оператори рівноцінні:
bguіr[і].weіght=70;
(bguіr+і)->weіght=70;
(*(bguіr+і)).weіght=70;
(per_ptr+і)->weіght=70;
(*(per_ptr+і)).weіght=70;
per_ptr[і].weіght=70;
тому що було виконане присвоювання per_ptr=bguіr; // per_ptr=&bguіr[0];
Вказівник на елемент структури має всі властивості звичайної змінної. Наприклад, якщо елемент структури є масивом символів, то per_ptr->fam - вказівником- константою на перший байт цього масиву, а per_ptr->fam[0]=’Я' запише в нього букву "Я". Сам же вказівник per_ptr визначає адресу першого байта структурної змінної й одночасно є адресою першого елемента, що входить у структуру. У нашому випадку - адресою fam[0]. Можна визначити адресу будь-якого елемента, що міститься в структурній змінній.
Наприклад:
struct person P1, P2, *р_Р1, *р_Р2;
р_Р1=&Р1; р_Р2=&Р2; // Установили вказівники на змінні
іnt *pі; // Оголошення
float *pf; // вказівників
char *pc; // на відповідний тип
//Їхня ініціалізація
pі=&p_Pl->heіght;
pf=&p_Pl->weіght;
pc=&p_P2->fam[0]; // або pc=p_P2->fam;
Можна визначити адресу змінної-структури, що є елементом іншої структури:
struct date *p_d=&p_P2->bіrthday;
p_d->day=15;
p_d->month=l ;
p_d->year=1980;
Ці оператори рівносильні наступним:
Р2.bіrthday.day=15;
Р2.bіrthday.month=l;
P2.bіrthday.year=1980;
Інший приклад оголошення шаблонів і присвоювання значень елементам змінних типу структура:
struct Poіnt /* Оголошуємо шаблони структур */
{ double х, y;
};
struct Rect
{ struct Poіnt Up_corn;
struct Poіnt Down_corn;
double length;:
double wіdth;
};
voіd maіn()
{ /* Оголошуємо змінні типу структура */
struct Poіnt p1, p2, p_arrl5];
struct Rect r1, r2;
struct Poіnt *p_ptr; // Оголошуємо вказівники
struct Rect *r_ptr; //на тип структури
// Виконання операцій над компонентами структури
pl.x=20;
p_arr[5].x=pl.x-10;
rl.Up_corn.x=p_arr[5].x;
rl.Down_corn.x-30;
rl.Up_corn.y=pl.x;
p_ptr=&pl;
p_ptr->y=35; // Використання вказівника або (*p_ptr).y=35;
p_ptr=p_arr;
(p_ptr+5)-> y-40; // (*( p_ptr+5M .y-40;
r_ ptr=&rl;
r_ptr->Down_corn.y=60; // (*(r_ptr)).Down_corn.y=60;
Структура не може містити в собі змінні свого типу, тобто наступне оголошення шаблона буде неправильним:
struct man { char *fam;
char *name;
struct man any; };
Однак структура може включати елементи, що є вказівниками на оголошений тип цієї ж структури:
struct man { char *fam;
char *name;
struct man *p_man; };
Можна визначити вказівник на оголошений тип структури й проініціалізувати його. Наприклад:
struct man group[20], studl, *pl, *p2;
pl=&studl;
p2=group; // або p2=Sgroup [0];
Установимо вказівник p_man структури group[0] на studl;
group[0].p_man " PІ; // або p2->p_man=Pl; або p2->p_man=&studl;
Нехай є визначення:
char famі[] = "Давидiв", namel[]="Андрій";
Тоді реалізація операторів:
p2->p_man->fam=faml;
p2->p_man->name=namel;
запише в структурну змінну studl у змінні fam і name відповідно адреси розміщення в пам'яті текстів "Давидiв" і "Андрій". Просування вказівника операцією ++ або -- відповідно збільшує або зменшує значення вказівника на розмір шаблона, тобто встановлює вказівник на початок іншого елемента масиву - наступного або попереднього.
Ініціалізація структур
При визначенні структурних змінних можна ініціалізувати їхні поля. Ця можливість подібна ініціалізації масиву й слідує тим же правилам:
ім'я_шаблона ім'я_змінної_структури = {значення1., значення2, . . .};
Компілятор присвоює значення1 першій змінній в структурі, значення2 - другій змінній й т.д., і тут необхідно додержуватися деяких правил:
1. значення, що присвоюються, повинні співпадати по типу з відповідними полями структури;
2. можна оголошувати кількість значень, що присвоюються, меншу, ніж кілкість полів. Компілятор присвоїть нулі іншим полям структури;
3. список ініціалізації послідовно присвоює значення полям структури, вкладених структур і масивів.
Наприклад:
struct date
{ іnt day, month, year;
} d[5] = { {1, 3, 1980),
{5, 1, 1990},
{1, 1, 1983}
};
/* Проініціалізовані перші три елементи масиву */
struct person bguіr[20]={
{"Бурба","Дмитро", "Леонідович", 75., 185, 15, 1, 1980},
{"Таболич", "Юрій", "Олександрович", 70., 180, 25, 8, 1983}};
/* Проініціалізовані 2 елементи масиву структур */
Об'єднання
Об’єднання подібне структурі, але в кожен момент часу може використовуватись (або є активним) тільки один з його компонентів. Тип об’єднання може задаватися записом виду
union МІТКА {
опис компонента а1;
опис компонента а2;
…………………….
опис компонента аn;
};
Для кожного з цих компонентів виділяється одна і та ж область пам’яті, тобто вони перекриваються. Хоча доступ до цієї області пам’яті можливий через використання будь-якого з компонентів. Компонент для цієї мети повинен вибиратися так, щоб отриманий результат не був беззмістовним.
Як вже зазначалось, об’єднання подібні структурам. Доступ до компонент об’єднання відбувається тим самим способом, що і для структур.
Об’єднання застосовуються:
для мінімізації об’єму пам’яті, що використовується, якщо в кожний момент часу тільки один об’єкт з багатьох є активним;
для інтерпретації основного представлення об’єкта одного типу, так ніби цьому об’єкту був присвоєний другий тип.
Роль об’єднання аналогічна ролі опису еквівалентності (equivalence) в Фортрані. Компоненти в об’єднанні взаємозв’язані точно так, як два об’єкта, зв’язані відношенням еквівалентності в Фортрані.
В якості прикладу визначення об’єкта типу union розглянемо об’єднання shape, яке визначається наступним чином:
struct position {
int x; // 2 байти
int y; // 2 байти
};
union {
float radius; // коло 4 байти
float a[2]; // прямокутник 4*2=8 байт
int b[3]; // трикутник 2*3=6 байт
struct position p; // координата точки 2+2=4 байти
} shape;
Розмір об’єднання shape дорівнює не суммі всіх байтів, як у випадку структури, а розміру найбільшого поля (компоненти), тобто 8 байт.
В цьому прикладі є сенс використовувати тільки той компонент, який отримав останнім своє значення.
Приклади програм з використанням структур
/* Приклад: створити масив даних про студентів групи. Записати ім’я, прізвище, рік народження кожного студента, оцінки по п’яти екзаменах. Визначити средній бал за сесію і відсортувати список по сумі балів. */
#include <stdio.h>
#include <iostream.h>
#include <conio.h>
struct student // Шаблон структури
{ char name[20]; // Ім’я
char fam[30]; // Прізвище
int year; // Вік
int mark[5]; // Оцінки
int average; // Сума балів
};
student students[30]; // Массив даних по групі
student buffer; // Додаткова структура
int records; // Кількість студентів в групі
int i, j ; // Допоміжні змінні
void main ( )
{ records = 0 ; // Спочатку записів немає
do // Заповняєм масив структур
{ cout << "Студент № " << records+1 << endl;
cout << "Введіть прізвище "; fflush(stdin) ;
cin >> students [records].fam;
cout << "Введіть ім’я ";
cin >> students [records].name;
cout << "Введіть вік ";
cin >> students [records].year;
for(i = 0; i < 5; i++)
{ cout << " Введіть оцінку по екзамену № " << i+1 << endl;
cin >> students[records].mark[i];
}
records++;
cout << "Прининити работу? [1/0] ";
cin >> i;
} while( i ) ;
for(i = 0; i < records; i++) // Підрахуєм суму балів
{ students [ i ].average = 0;
for (j=0; j < 5; j++)
students[i].average += students[i].mark[j ];
}
for (i = 0; i < records-1; i++) // Відсортуєм
for (j = i; j < records; j++) // по сумі балів
if (students[i].average > students[j].average)
{ buffer = mass[i]; // Перестановка елементів
mass[i] = mass[j]; // структури, особливість
mass[j] = buffer; // мови C/C++
}
for (i = 0; i < records; i++) // Виводимо результат
{ cout << "\n студент " << students[i].name << “\t" << students [i].fam ;
cout << " Вік " << students [i].year;
cout << " Сер. бал " << students[i].average;
}
}
// Приклад: ініціалізація масиву структур, ввід елементів структури.
#include <iostream.h>
void main ()
{ struct complex {double r, i; }cmplx[2] [3]=
{1,2,3,4,5,6,7,8,9,10,11,12);
int i, j;
for(i = 0; i<2; i++)
{ for (j=0; j < 3; j++)
cout << cmplx[i] [j].r << " " << сmр1х[I][j].i. << “ ";
cout << endl;
}
struct comp{double r,i;}cmp[2][3]=
// {{1,2}, {3,4}, {5,б) {7,8}, {9,10}, {11,2}}; !!! -- error
// { {{1,2},{3,4}, {5,б} {7,8}, {9,10}, {11,2}} }; !!! -- error
{ {{11,21), {31, 41}, {51,61}}, {{71,81}, {.91,101}, {111,121}} };
for (i =0; i<2; i++)
{ for (j =0; j<2; j++)
cout << cmp[i][j].r << " " << cmp[i][j].i << " ";
cout << endl;
}
}
/* Приклад: ввести масив структур зі списком студентів і оцінками. Розсортувати масив структур в алфавітному порядку прізвищ, переміщаючи вказівники на структури. */
#include <conio.h>
#include <iostream.h>
#include <string.h>
typedef struct_student
{ char fio[80]; int mark[5]; } student;
void main ()
{ int i, j, k, size;
student. st[100], * stp[100], *tmp;
do { cout << endl << "Введіть кількість студентів: ";
cin >> size;
} while (size > 100);
for (i=0; i<size; i++)
{ fflush (stdin);
cout << " \n Введіть ПІП студента: "; //- вводимо дані
cin >> st[i].fio;
cout << "\n Введіть його оцінки: ";
for (j = 0; j<5; j++) cin >> st[i].mark[j];
stp[i] = &st[i]; // Зберігаєм вказівник на структуру
}
for (i=0; i<size-l; i++)
for (j=i+1; j<size; j++)
if( strcmp(stp[i]->fio.stp[j]->fio)>0)
{ tmp=stp[i]; stp[i]=stp[j]; stp[j]=tmp; }
cout << '' \n Результат виконання:'';
for (i=0; i<size; i++)
{ cout << endl << endl << stp[i]->fio << “ Його оцінки: ";
for (j=0; j<5; j++) cout << stp[i] -> mark[j] << “ “;
}
}
/*Приклад: ввести масив структур. Розсортувати його в алфавітному порядку прізвищ, які входять в структуру, перемiщаючи самі структури.*/
#include <stdio.h>
#include <iostream.h>
int strcmp(char *sl, char *s2);
struct st{ char name[80]; int age; };
void main ()
{ struct st m[100], t;
int i, j, k;
cout<< endl << "k-?";
cin >> k;
for(i = 0; i < k; i++) {fflush(stdin); cin >> m[i].name >> m[i].age; }
// Сортуємо "бульбашкою"
for (i = 0; i < k-l; i++)
for(j = i+l; j < k; j++)
if (strcmp(m[i]).name, m[j].name) > 0)
{ t=m[i]; m[i] = m[j]; m[j] = t; }
// Виводимо результат *
cout << " Sorted:" << endl;
for (i = 0; i < k; i++)
{ cout << m[i].name << "years " <<.m[i].age << endl; }
int strcmp(char *s1, char *s2)
{ for( ; *s1 == *s2; sl++, s2++)
if ( !*s1 ) return 0;
return *s1 - *s2;
}
/* Приклад: при використанні вказівників для вводу даних з плаваючою крапкою в змінній типу структура можуть бути помилки на етапі виконання. Для їх усунення до використання вказівника необхідно при виконанні програми ввести значення фіктивної змінної з плаваючою крапкою:
якщо забрати scanf("%f",&x); то;
// Abnormal program termination
// floating point formats not linked */
#include<stdio.h>
struct mas { int a[5];
float b[5];
} *pl, *p2, *p;
struct res { int maxi, mini;
float maxf, minf;
} *p3;
void main ( )
{ int i, j = 2; float x;
struct mas s, e;
struct res d;
p3 = &d; p1 = &s; p2 = &e; p = p2;
puts (“\n a-?”); scanf(“%f”, &x); //-----!!!!!!!!!
while ( j )
{ j --;
puts ("\n a-?") ;
for (i=0; i<5; i++)
{ scanf ("%d",&(p->a[i]) );
printf(" %d ", p -> a[i]) ;
}
puts ("\n b-?");
for (i=0; i<5; i++)
{ scanf ("%f",&p->b[i] );
printf(" %f ", p -> b[i]) ;
}
p=p1;
}
}
/* Приклад: в цій програмі показуються різні способи доступу до елементів структури. */
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
struct st { char priz[10];
float f; int k;
};
void main ( )
{ st s[3]; int i;
for(i=0; i<3; i++)
cin >> (char*) &s[i]; // Ввiд priz
for(i=0; i<3; i++)
cout << endl << (char*) &s[i]; // Вивід priz
void *p_st = s; // Вказівник без типу
for(i=0; i<3; i++)
{ cout << endl << (char*)p_st; // Вивід, використовуючи вказівник
(( st *)p_st)++;
}
p_st = s;
for(i=0; i<3; i++)
{ cout << (char*)p_st; // Вивід, використовуючи вказівник
(( st *)p_st)++;
} }
/* Приклад: способи доступу до елементів структури, які використовуються у C/C++. */
#include <iostream.h>
struct dt { double r, i; };
struct fio { char f[l0];};
struct std ( dt d;
fio fn;
std *p;
};
void main ( )
{ dt *pd, x; fio *pf;
// std z = {l,2,"qwertn} ,*ps = &z; ps -> p = ps;
std z = {l,2,"qwert"},*ps; ps -> p = ps = &z;
double *pi; pi = &(z.d.r);
cout << “---“<< z.d.r << " " << z.d.i << " n << z.fn.f << " ";
cout << “***" << ps ->d.r << " “ << (*ps) .d.i << " ” << ps -> p-> fn.f << " " << *pi;
cout << endl; x=z.d;
pd=&z.d; cout << " +++” << pd -> r << “ “ << (*pd).i << “ “;
cout << endl;
}
/* Приклад: допустимі способи вводу стрічок і елементів структур. Вивід компонентів структури на екран. */
#include <stdio.h>
#include <iostream.h>
void main(void)
{ char pr, t[11], st[5];
struct x{char a[16], b[16], c[16]; } z, *pz;
pz=&z;
cout << "&z= " << &z << "&z.a= " << &z.a <<
"&z.b= " << &z.b << “ &z.c=" << &z.c << endl;
cout << "&pr= " << &pr << "&st= " << &st << " &t= " << &t << endl;
while(1)
{ cout << "Bвід 3-х стрічок";
сin >> st[0] ;
cout << "&st[0] (s) = "<< &st[0] << "&st[0] (p) ="
<< &st[0] << "pr(c)= " << pr;
cout << endl;
cin >> st;
cout << " st(s) = " << st << "st(p) = " << st << "pr(c) = " << pr << endl;
cin >> &st;
cout << "&st(s)= " << &st << "st(s) = " <<
st << " pr(c) = " << pr << endl;
cout << "&t(s) =" << &t << " t(p) = " << t << "pr(c) = " << pr << endl;
fflush (stdin) ;
switch(pr=getchar())
{ case 'q': return;
case ' r': cin >> z