Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
lektsii_OP / T13.doc
Скачиваний:
91
Добавлен:
17.03.2016
Размер:
364.54 Кб
Скачать

Структури-параметри

Структури можуть передаватися до функцій як параметри, а також повертатися ними, як результат. При цьому передача структури в якості параметра у функцію можлива як за іменем, так і за покажчиком. Наприклад,

void f1(TStudent stud);

void f2(TStudent *ps);

TStudent f3(TStudent stud).

Нехай, програма передає структуру типу employee у функцію з ім'ям get_employee_id, яка запитує у користувача ідентифікаційний номер службовця і потім присвоює цей номер елементу структури employee_id. Щоб змінити елемент, функція працює з покажчиком на структуру:

struct employee

{ char name[64];

long employee_id;

float salary;

char phone[10];

int office_number;

};

void get_employee_id(employee *);

void main(void)

{ employee worker;

get_employee_id(&worker) ;

strcpy(worker.name, "Іванов"); // Копіювати імя в рядок

cout << "Службовець: " << worker.name << endl;

cout << "Номер службовця: " << worker.employee_id << endl;

system("pause");

}

void get_employee_id(employee *worker)

{ cout << " Введіть номер службовця: ";

cin >> worker->employee_id;

}

Як видно, всередині функції main програма передає змінну worker типу структури в функцію get_employee_id за допомогою адреси. Усередині функції gel_employee_id значення, введене користувачем, присвоюється елементу employee_id за допомогою наступного оператора:

cin >> worker->employee_id;

У деяких мовах програмування, зокрема, С/С++, можлива передача окремих елементів структур як аргументів функції.

Розглянемо структуру, яка описує певну дату:

struct TDate

{ int day;

int month;

int year;

}d;

і функцію, яка обчислює день року по заданій даті:

int day_of_year (int day, int month, int year)

{ …

}

Тоді для функції day_of_year можливе звернення у вигляді:

day_of_year (d.day, d.month, d.year).

Коли структури або окремі їх елементи передаються функції, їх передача здійснюється по значенню. А тому функція, що викликає, не може змінювати елементи у структурі, що викликається. Щоб здійснити передачу структури по посиланню, необхідно передати її адресу.

Масиви структур автоматично передаються за посиланням.

Слід зазначити, що, якщо у функцію передається покажчик на масив структур, то він ініціалізується значенням нульового елемента масиву:

тип_структури *ім'я_покажчика;

покажчик = &ім'я_масиву[0];

Приклад.

struct lib

{ char *last_name;

char *first_name;

int number;

};

void print_data(lib *);

int main ()

{ lib data[3]={“Last_Name1”, “First_Name1”, 1, ”Last_Name2”, “First_Name2”, 2,

“Last_Name3”, “First_Name3”, 3 };

print_data(data+2); //адреса 3-го елемента

getch();

}

void print_data(lib *d)

{ printf (“Last name is %s and first name is %s”, d->last_name, d->first_name);

}

Об’єднання

Іноді виникає необхідність використовувати об'єкти-записи, чиї набори властивостей багато у чому схожі, але мають певні відмінності. При цьому обробка спільних властивостей таких об'єктів має здійснюватися за одними і тими ж алгоритмам, специфічні ж властивості мають оброблятися по-різному.

У такому випадку можна описати всі групи об'єктів одноманітно, включивши в опис набори властивостей для всіх груп. Але такий опис буде неекономічним з точки зору використання оперативної пам'яті і незручним з логічної точки зору. Якщо ж описати кожну групу власною структурою, втрачається можливість обробляти загальні властивості за єдиними алгоритмами.

Для задач подібного роду розвинені мови програмування, зокрема, Pascal, Ada, C/С++, мають спеціальний інструментарій – комбіновані структури з варіантами. Такі структури даних об'єднують записи, що мають схожу, але не ідентичну структуру.

Комбіновані структури з варіантами складаються з двох частин: фіксованої і варіативної. У фіксованій частині описуються поля, загальні для всіх груп об'єктів. Серед цих полів, зазвичай, буває поле, значення якого дозволяє ідентифікувати групу, до якої даний об'єкт належить і, відповідно, який з варіантів другої частини комбінованої структури повинен бути використаний при обробці. Варіативна частина містить описи непересічних властивостей: для кожної підмножини таких властивостей - окремий опис.

При реалізації комбінованих структур з варіантами доступним у будь-який момент часу, окрім фіксованої частини, є тільки один із варіантів такої структури.

Механізм реалізації комбінованих структур з варіативною частиною в мовах сімейства Pascal більш строгий: у визначенні запису явно вказується спеціальне поле, значення якого є мітками відповідних варіантів типу запису. Формат оголошення записів з варіантами у Pascal:

RECORD

список_полів_1: тип;

...

список_полів_n: тип;

CASE поле_ознаки: тип OF

варіант_1:(список_полів_1: тип;

...

список_полів_n: тип; );

...

варіант_n:(список_полів_1: тип;

...

список_полів_n: тип; );

END.

Наприклад, нехай потрібно розміщувати на екрані прості геометричні фігури - кола, прямокутники, трикутники. Для опису стану екрана у цьому випадку зручно представляти всі фігури однотипними записами: для будь-якої фігури її опис має включати в себе координати деякої опорної точки (центру, правого верхнього кута, однієї з вершин) і код кольору; інші ж параметри побудови будуть різними для різних фігур (для кола – радіус, для прямокутника - довжини непаралельних сторін або координати лівого нижнього кута, для трикутника - координати двох інших вершин тощо).

Запис з варіантами для такої задачі в мові Pascal виглядає так:

type TFigure = record

fig_type : char; { тип фігури }

x0, y0 : word; { координати опорної точки }

color : byte; { колір }

case fig_t : char of

'C': ( radius : word); { радіус кола }

'R': (len1, len2:word); { довжини сторін прямокутника }

'T': (x1, y1, x2, y2 : word); { координати двох вершин трикутника }

end;

При використанні записів з варіантами у Pascal необхідно дотримуватися наступних правил:

  • всі імена полів повинні відрізнятися один від одного, навіть якщо вони зустрічаються в різних варіантах;

  • запис може мати тільки одну варіантну частину, оголошену в кінці.

Більш слабкий, але еквівалентний за можливостями механізм підтримується в мовах сімейства С/С++. У цих мовах існує спеціальний різновид структур – так звані об’єднання. Об'єднання – це структура, що складається з фіксованої кількості полів одного або декількох типів, у якої доступним у будь-який момент часу є тільки одне поле. При цьому всі поля об’єднання розміщуються в одній спільній області памяті.

Обсяг пам'яті, що виділяється під об’єднання, відповідає розміру поля найбільшої довжини.

Оголошуються об’єднання подібно структурам, але за допомогою ключового слова union. Формат оголошення такий же, як і у випадку структур:

union [тип_об’єднання]

{ тип поле_1;

...

тип поле_n;

} ;

Наприклад,

union S

{ int i;

char ch;

long int L;

};

Розташування елементів даного об’єднання в пам'яті буде наступним (рис. 5):

Рис. 5. Виділення пам'яті для об'єднання

Тип члена об’єднання може бути довільним, в тому числі структурою або іншим об’єднанням. Окрім того, об'єднання можуть з'являтися в середині структур, а структури в середині об’єднань.

Таким чином, у С/С++ тип даних, подібний до записів з варіантами, є структурою, яка може бути реалізована з використанням комбінації структури та об'єднання. Зокрема, для прикладу з геометричними фігурами, структура з варіантами у мові С/С++ може бути описана так:

struct TFigure

{ char figure; // тип фігури

unsigned int x0, y0; // координати опорної точки

unsigned char color; // колір

union

{ struct

{ unsigned int radius; // радіус кола

} cyrcle;

struct

{ unsigned int len1, len2; // довжини сторін прямокутника

} rectangle;

struct

{ unsigned int x1, y1, x2, y2; // координати двох вершин трикутника

} triangle;

} ;

};

У разі, коли об’єднання є складовою частиною іншої структури, поле ознаки, яке визначатиме, який з елементів об’єднання активізується, входить до складу цієї структури. Окрім того, використання структур в середині об’єднань вимагає іменування кожного такого варіанта.

Виділення пам'яті для наведених вище записів (структур) з варіантами показано на рис.6.

Рис. 6. Виділення пам'яті для записів з варіантами

Як видно із рисунка, під запис з варіантами виділяється в будь-якому випадку обсяг пам'яті, достатній для розміщення найбільшого його варіанту. Якщо ж виділена пам'ять використовується для меншого варіанту, зайва її частина залишається невикористаною. Тобто, усі варіанти якби "накладаються" один на один, розподіляючи одну і ту ж область пам’яті.

Таким чином, об'єднання дуже схожі на структури, за винятком того, як вони зберігаються у пам'яті: об'єднання є множиною полів різного типу, розташованих в одній області пам'яті; тоді як поля структури розташовуються в пам'яті послідовно.

Оголошення змінної-об’єднання відповідає наступному синтаксису:

union тип_об’єднання ім’я_змінної // стандарт С

тип_об’єднання ім’я_змінної // стандарт С++.

Можна об’єднувати опис типу та оголошення змінної-об’єднання:

union [тип_об’єднання]

{ тип поле_1;

...

тип поле_n;

} ім’я_змінної;

Ініціалізація об'єднання, як і структури, може бути виконана при оголошенні. При цьому об'єднання можна ініціалізувати тільки значеннями, що відповідають типу полів його першого варіанта. Наприклад,

union tag

{ int i;

double d;

} u={10};

Визначення об’єднання здійснюється, як правило, на початку програми. Тому воно та його члени можуть використовуватися для роботи і оголошення змінних у всіх функціях програми.

Доступ до членів об'єднання здійснюється аналогічно структурам, тобто через крапкову нотацію або операцію «–>» (доступ до членів варіантної частини записів з варіантами Pascal здійснюється тільки через крапкову нотацію).

Враховуючи, що мова C/С++, на відміну від Pascal, вимагає іменування кожного варіанта об'єднання, то ідентифікація поля, що знаходиться в його варіантній частині, є ускладненою, порівняно із звертанням до поля, що знаходиться в варіантній частини запису з варіантами Pascal1. Зокрема, крапкова нотація такого звертання має вид:

ім'я_змінної-об'єднання.ім'я_варіанту.ім'я_поля.

Так, якщо в наведеному вище прикладі визначена змінна fig типу TFigure, в якій зберігається опис окола, то звернення до радіусу цього кола в мові Pascal буде мати вигляд fig.radius, а в C/С++ - fig.circle.radius.

При зверненні до полів об'єднання слід пам'ятати, яке поле розміщувалося в пам'яті останнім. Якщо намагатися витягувати дані за допомогою поля іншого типу, значення може бути прочитане неправильно. Наприклад,

union tag

{ int i;

double d;

} u={1.2};

printf("%d\n",u.i); // виведення числа 1

printf("%lf\n",u.d); // виведення числа 0

Відповідь «1» отримана тому, що ініціалізація повинна відповідати типу першого поля - int; відповідь «0» - наслідок того, що дані типу int прочитані полем типу float неправильно.

Дозволяється використовувати масиви структур, що містять об’єднання. Тоді у масиві структур, означеному як

struct

{ char *name;

int flags;

int utype;

union

{ int ival;

float fval;

char *sval;

} u;

} symtab[NSYM];

до поля ival можна звернутися як

symtab[i].u.ival,

а до першого символу рядка sval одним із наступних способів:

*symtab[i].u.sval

symtab[i].u.sval[0].

Якщо для слідкування за тим, який тип у дану мить збережено в об'єднанні u використати змінну utype, тоді відповідний код може бути наступним:

if (utype == INT)

printf("%d\n", u.ival);

if (utype == FLOAT)

printf("%f\n", u.fval);

if (utype == STRING)

printf("%s\n", u.sval);

else

printf("bad type %d in utype\n", utype);

Нехай треба створити масив структур. Кожна структура складається із таких елементів: прізвище викладача, назва дисципліни, яку він викладає, прізвище студента, що вивчає дану дисципліну. Для викладача також задається його посада та зарплатня, для студента - середній бал успішності за останню сесію та стипендія.

Оголосити такий масив можна наступним чином:

struct teacher //викладач

{ char fio[40]; //прізвище

char posada[20]; //посада

float salary; //зарплатня

};

struct student //студент

{ char fio[40]; //прізвище

float bal; //середній бал

float stipend; //стипендія

};

struct subject //дисципліна

{ char name[30]; //назва дисципліни

teacher prepod; //викладач, що читає дисципліну

int num; //кількість студентів, що вивчають дисципліну

student stud [20] ; //студенти, що вивчають дисципліну

};

subject predmet[10]; //масив дисциплін

Використання об'єднань дозволяє спростити опис такого масиву, оголосивши його, наприклад, наступним чином:

enum individ {TEACHER, STUDENT}; //статус особи

struct person //структура особи

{ char fio[10]; //прізвище

float finance; //зарплатня (стипендія)

individ tag; //ознака особи

union // об’єднання даних про викладача та студента

{ struct

{ char posada[20]; //посада викладача

} tutor;

struct

{ float bal; //середній бал студента

} stud;

};

};

struct subject // дисципліна

{ char name[10]; //назва дисципліни

person people[10]; //викладач, що читає предмет

int num; //кількість студентів, що вивчають дисципліну

};

subject predmet[10]; //масив дисциплін

Приклад. В залежності від виду графічної фігури визначити: для кола – його радіус, для прямокутника – площу; для трикутника – периметр.

#define CIRCLE 1

#define RECT 2

#define TRIANGLE 3

struct TFigure

{ int area, perimeter; // загальні компоненти

int type; // признак активного компонента

union

{ int radius; // колс

int a[2]; //прямокутник

int b[3]; // трикутник

} geom_fig;

};

TFigure fig;

void input ();

void output ();

int main ()

{ input ();

output ();

system ("pause");

}

void input ()

{ cout << "Введіть значення признаку активного компонента: ";

cin >> fig.type;

switch (fig.type)

{ case CIRCLE:

cout << "Введіть радіус:";

cin >> fig.geom_fig.radius;

break;

case RECT:

cout << "Введіть сторони прямокутника:";

cin >> fig.geom_fig.a[0] >> fig.geom_fig.a[1];

fig.area = fig.geom_fig.a[0]*fig.geom_fig.a[1];

break;

case TRIANGLE:

cout << " Введіть сторони трикутника:";

cin >> fig.geom_fig.b[0] >> fig.geom_fig.b[1] >> fig.geom_fig.b[2];

fig.perimeter=fig.geom_fig.b[0]+fig.geom_fig.b[1]+ fig.geom_fig.b[2];

break;

default: cout << "Помилка!";

}

}

void output ()

{ cout << "Введіть значення признаку активного компонента: ";

cin >> fig.type;

switch (fig.type)

{ case CIRCLE:

cout <<"Радіус: " << fig.geom_fig.radius;

break;

case RECT:

cout << "Площа: " << fig.area;

break;

case TRIANGLE:

cout << "Периметр: " << fig.perimeter;

break;

default: cout << "Помилка!";

}

}

Об'єднання часто використовують при створенні прикладних програм як списки місяців, окремих характерних властивостей, власних імен чи списків інших об’єктів.

При їх використанні також з'являється можливість інтерпретувати одні й ті ж дані по різному. Зокрема, використання об'єднань дає можливість інтерпретувати один і той же набір бітів декількома різними способами.

Приклад. З клавіатури вводиться символ. Вивести на екран двійковий код цього символу.

#include <stdlib.h>

#include <stdio.h>

#include <conio.h>

#include <iostream>

using namespace std;

struct TByte // структура бітових полів

{ int b1:1;

int b2:1;

int b3:1;

int b4:1;

int b5:1;

int b6:1;

int b7:1;

int b8:1;

};

union TBits

{ char ch;

byte cod;

} u;

void decode(TBits x); // прототип функції декодування

int main()

{ do { u.ch=getche();

printf(" :");

decode(u);

} while(u.ch!==’q’);

}

void decode(TBits u) // функція декодування

{ if (u.cod.b8) printf("1"); else printf("0");

if (u.cod.b7) printf("1"); else printf("0");

if (u.cod.b6) printf("1"); else printf("0");

if (u.cod.b5) printf("1"); else printf("0");

if (u.cod.b4) printf("1"); else printf("0");

if (u.cod.b3) printf("1"); else printf("0");

if (u.cod.b2) printf("1"); else printf("0");

if (u.cod.b1) printf("1"); else printf("0");

printf("\n");

}

У цій програмі змінна-об'єднання u містить два елементи: символьне поле ch і бітову структуру cod, які накладаються один на одного. Таким чином, виявляється можливим отримати доступ до кожного біту коду символу. Робота програми закінчується після введення символу q. Ось варіант результатів роботи даної програми:

s: 01110011

d: 01100100

J: 01101010

a: 01100001

b: 01100010

с: 01100011

d: 01100100

q: 01110001

1Звертання до поля, що знаходиться в варіативній частини запису з варіантами Pascal, нічим не відрізняється від звернення до поля простого запису: ім'я_змінної-запису.ім'я поля

18

Соседние файлы в папке lektsii_OP