asyan.org
добавить свой файл
1
Лекція 13.

Похідні типи. Структури та об’єднання.

З основних типів мови С++ користувач може конструювати похідні типи, наприклад, структури та об'єднання. Разом з масивами та класами структури та об’єднання відносяться до структуризованих типів.

Структура як тип та сукупність даних.

Структура – це об’єднана в одне ціле множина поіменованих елементів у загальному випадку різних типів. Порівнюючи структуру з масивом, потрібно відмітити, що масив – це сукупність однорідних об’єктів, що має загальне ім’я – ідентифікатор масива. Іншими словами, сві елементи масива є об’єктами одного типу. Це не завжди зручно, оскільки виникає потреба у використанні єдиного цілого, де всі дані мають різну довжину та різні типи. Об’єднати такі різнорідні дані зручно у структурі. Кожна структура містить у собі один чи кілька об’єктів (змінних, масивів, вказівників, структур) , що назіваються елементами структури. Відомості про дані, що входять у структуру, можна відтворити структурним типом.

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

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

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

ім’я_структури.ім’я_елемента_структури

Наприклад, якщо визначити структуру, що описує бібліографічну картку:

struct card {char *autor; // П.І.Б. автора книги

char *title; //Заголовок книги

char *city; //Місце видання

char *firm; //Видавництво

int year; //Рік видання

int pages; //Кількість сторінок

} с;

тоді, звернутися до елемента year структури card можна через змінну с таким чином: с.year=1998. Можна також надрукувати це значення: cout<
При визначенні структур можливе ініціювання, тобто надання початкових значень їх елементам. Наприклад, оголосивши структурний тип card, можна таким чином визначити та ініціювати конкретну структуру:

card distionary= {“Hornby A.S.”, “Oxford students distionary of Current English”, \

“Oxford”, “Oxford University”, 1984, 769};

Таке визначення еквівалентне наступній послідовності операторів:

сard distionary; distionary.author=”Hornby A.S.”;

distionary.title=“Oxford students distionary of Current English”;

distionary.city= “Oxford”; distionaru.year=1984; distionary.pages=769;

Важливо розрізняти ім’я конкретної структури distionary та ім’я структурного типу сard. З іменем структурного типу не поєднаний ніякий об’єкт, і тому через нього не можна звертатись за уточненим іменем елементів. Визначення структурного типу оголошує лише шаблон (формат, внутрішню побудову) структур. Ідентифікатор сard у нашому прикладі – це назва структурного типу, тобто “ярлик” чи “етикетка” структур, які будуть визначені у програмі. Для пояснення різниці між іменем структурного типу та іменем конкретних структур, що мають такий тип, у англомовній літературі по мові С++ для позначення структурного типу використовують тармін tag (ярлик, етикетка, бірка). У наведеному прикладі визначення структурного типу поєднане з визначенням конкретної структури такого типу с.

Оскільки ім’я структурного типу має всі права імен типів, тому можна визначати вказівники на структури:

ім’я_структурного типу * ім’я_вказівника_на_структуру;

Вказівник, що визначається, можна ініціювати. Значенням кожного вказівника на структуру може бути адреса структури того типу, скажемо так, номер байта, починаючи від якого структура розташована в пам’яті. Структурний тип вказує її розміри, визначаючи, на яку величину ( на скільки байтів) зміниться значення вказівника на структуру, якщо до нього додати 1 ( чи відняти від нього 1).

Після визначення такого вказівника виникає ще одна можливість доступу до елементів структури с через операцію ‘->’ доступу до елемента структури, з якою на дану мить поєднаний вказівник. Формат відповідного виразу такий: ім‘я_вказівника -> ім’я_елемента_структури

Наприклад с->firm

Інша можливість звернення до елемента структури через вказівник, що її адресує – це розйменування вазівника ‘*’ та створення уточненого імені такого вигляду: (*ім’я_вказівника).ім’я_елемента_структури

Важливим є використання круглих дужок, оскільки операція розйменування повинна відноситися лише до імені вказівника, а не до уточненого імені елемента структури. Таким чином, наступні три вирази еквівалентні: card ptrcard=&c; (ptrcard).firm

ptrcard->firm

c.firm

Всі вони відносяться до одного елементу firm конкретної структури card.

Як і для інших об‘єктів, для структур можна визначати посилання:

ім’я_структурного_типу& ім’я_посилання_на структуру ініціювач

Наприклад, для введеного вище структурного типу можна ввести посилання card& fc=c; і після такого визначення до елемента year можна звертатися як через c.year, так і через fc.year.

Всі елементи структури розташовані в пам’яті послідовно, їм загалом відводиться саме стільки пам’яті, скільки структурі в цілому.

У відношенні до елементів структур існує практично лише одне обмеження – елемент структури не може мати той же тип, що й визначений структурний тип., тобто помилковим буде визначення: struct v{ v s; int n;}; Але можливе оголошення вказівника на структуру, що визначається, наприклад: struct cor{cor *pc; long f;};

Елементом структури, що визначається, може бути структура, визначена раніше, наприклад: struct beg{int k;char *h;} st; struct nex{beg b; float d;};

Якщо у визначенні структурного типу потрібно використати вказівник на структуру іншого типу як елемент, тоді дозволяється така послідовність визначень struct A; //Неповне визначення структурного типу

struct B {struct A *pa;};

struct A{struct B *pb;};

Неповне визначення структурного типу A можна використовувати у визначенні структурного типу B, оскільки визначення вказівника ра на структуру типу A не вимагає відомостей про розмір структури типу A.

Наступне визначення у тій же програмі структурного типу A обов’язкове. Використання у структурах типу A вказівників на структури вже введеного типу В не потребує пояснень.

Розглядаючи “взаємовідношення” структур та функцій, маємо дві можливості: значення, що повертає функція, та параметри функції. Функція може повертати структуру як результат: struct h{char *n; int num;};

h func(void); // Прототип функції

Функція може повертати вказівник на структуру:

h *fun(void); //Прототип функції

Функція може повертати посилання на структуру:

h& f(void); //Прототип функції

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

void f1 (h str); //Пряме використання

void f2(h *pst); //Через вказівник

void f3 (h& rst); //Через посилання

Застосування посилання на об’єкт як параметра дає змогу уникнути повторення об’єкта в пам’яті.

До структур застосовується операція new відведення динамічної пам’яті, операндом для неї може бути структурний тип. У такому випадку відводиться пам’ять для структури заданого типу, і операція new повертає вказівник на відведену пам’ять. Пам’ять можна відводити і для масиву структур, наприклад:

L=new card[20];// Пам’ять для масива структур

У такому випадку операція new повертає вказівник на початок масива.

Об’єднання різнотипних даних.

Об’єднання оголошуються через службове слово union, наприклад, union{ long L; int t,r; char c[4];} un;

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

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

Для об’єднань вводиться поєднуючий тип:

union ім’я_поєднуючого типу {елементи_об’єднання}

Приклад поєднуючого типу:

union mix {double d; long[2]; int K[4];};

Після оголошення типу об’єднання можна визначати конкретні об’єднання, їх масиви, а також вказівники та посилання на об’єднання:

mix mA, mB[4]; // Об’єднання та масив об’єднань

mix *pmix; //Вказівник на об'єднання

mix& rmix=mA; //Посилання на об’єднання

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

або конструкцію, що містить вказівник:

вказівник_на об’єднання->ім’я_елемента

(*вказівник_на_об’єднання).ім’я_елемента

або конструкцію, що містить посилання:

посилання_на_об’єднання.ім’я_елемента

Приклади:

mA.d=64.8; mB[2].E[1]=10L; pmix=&mB[0]; pmix->E[0]=66;

cin>>(*pmix).K[1]; cin>>rmix.E[0];

Основна перевага об’єднання – можливість по-різному розпізнавати один вміст (код) дільниці пам’яті, тому основним призначенням об’єднань є доступ до однієї дільниці пам’яті через об’єкти різних типів. Потреба у такому механізмі виникає, наприклад, при віділенні з внутрішнього відтворення (з коду0 об’єкта заданого фрагмента.

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

union comp {long L; int I[2]; char C[4];}; comp m1={11111111 };

Дозволяється створювати масиви об’єднань та ініціювати їх:

comp mi[]={1L,2L,3L,4L };

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

Бітові поля структур та об’єднань.

У структурах та об’єднаннях можна використовувати як компоненти (елементи) бітові поля. Кожне бітове поле є цілим чи беззнаковим цілим значенням, яке розташоване в пам’яті у фіксованій кількості бітів (у компіляторі ВС++ від 1 до 16 бітів). Бітові поля можуть бути лише елементами структур, об’єднань та класів (побачимо пізніше), тобто бітові поля не можна оголошувати як самостійні об’єкти програми. Бітові поля не мають адрес, тобто для них не визначена операція ‘&’, немає посилань та вказівників на бітові поля. Їх не можна об’єднувати у масиви. Призначення бітових полів – забезпечити зручний доступ до окремих бітів даних. Через бітові поля можна створювати об’єкти з довжиною внутрішньої побудови, не кратною байту. Це дає змогу стискати інформацію з метою заощадження пам’яті.

Структура з бітовими полями визначається таким чином:

struct {тип_поля ім‘я_поля:ширина_поля;

тип_поля ім‘я_поля:ширина_поля; …}ім’я_структури;

Тут тип_поля – один з базових цілих типів int, unsigned int (скорочено unsigned), signed int(скорочено signed), char, short, long та їх знакові та беззнакові варіанти.

ім’я_поля – ідентифікатор, що вибирається користувачем;

ширина_поля – ціле невід’ємне десяткове число, значення якого не перевищує довжини слова конкретної ЕОМ.

Приклад запису бітових полів у структурі: struct {int a:10; int b:14; } xx, *px;

Діапазон можливих значень ширини_поля та порядок розташування полів структури суттєво залежать від реалізації.