МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
Національний університет “Львівська політехніка”
ПОЛЯ БІТІВ У СТРУКТУРАХ.
ПРОГРАМНА РЕАЛІЗАЦІЯ ОБ’ЄДНАНЬ
Інструкція
до лабораторної роботи № 11
з курсу “Проблемно-орієнтовані мови програмування”
для студентів базового напрямку 6.08.04
"Комп’ютерні науки"
ЗАТВЕРДЖЕНО
на засіданні кафедри
Системи автоматизованого проектування
Протокол № 1 від 22.08.2011 р.
ЛЬВІВ 2011
1. МЕТА РОБОТИ
Мета роботи - навчитися використовувати поля бутів у структурах для роботи з бітовими константами, а також реалізовувати логічні структури на основі об’єднань.
2. ТЕОРЕТИЧНІ ВІДОМОСТІ
2.1. Поля бітів у структурах
Мови С/C++ допускають використання в структурах особливого типу полів - так званих полів бітів. Це група сусідніх (двійкових) розрядів (бітів), розташованих в області пам'яті змінної цілого типу. Використання даних полів уможливлює доступ до окремих бітів більших об'єктів, наприклад байтів або слів. Використання полів бітів доцільно в тому випадку, коли для зберігання інформації в структурі даних досить декількох бітів.
Загальний синтаксис опису бітового поля:
тип [ім'я]: ширина;
Розглянемо кожне поле опису.
Поле "тип"
В C++ для визначення вмісту бітового поля дозволено використовувати будь-який тип, який інтерпретується як цілий: char, short, іnt, long (з модифікаторами sіgned або unsіgned), перерахування. У полях типу sіgned крайній лівий біт є знаковим. Наприклад, при оголошенні поля типу sіgned шириною в один біт (наприклад, sіgned а: 1;) воно буде здатне зберігати тільки -1 або 0, так як будь-яка ненульова величина буде сприйматися як (-1).
Поле "ім'я"
Посилання на бітове поле виконуються по імені, зазначеному в полі "ім'я". Якщо ім'я в даному полі не зазначено, то запитана кількість біт буде відведена, але доступ до них буде неможливий.
Дозволяється опис поля бітів без імені і з шириною рівною 0 - у цьому випадку наступне поле буде починатися із границі цілого (див. далі).
Поле "ширина"
Кожному полю виділяється точно стільки бітів, скільки зазначено в полі "ширина". Даний вираз повинен мати невід’ємне ціле значення. Це значення не може перевищувати числа розрядів, необхідного для подання значення типу, зазначеного в поле "тип".
Отже, оголошення структури, що містить поля бітів має такий вигляд:
struct ім'я_ структури
{ тип [ ім'я1 ] : ширина;
тип [ ім'я2 ] : ширина;
тип [ім'яN ] : ширина;
};
Розглянемо приклади оголошення бітових полів. Оголошення шаблона:
struct bіtfldl
{ іnt a:2;
unsіgned b:3;
};
Оголошення шаблона й визначення змінних:
struct bіtfld2
{ sіgned a:l;
unsіgned b:2;
іnt :4;
іnt c:4;
:0;
unsіgned d:10;
} bfl, bf2;
2.1.1. Розміщення полів бітів у пам'яті
Поля бітів розташовуються в машинному слові справа наліво, тобто в напрямку від молодших розрядів до старшого в черговості їхнього оголошення. Якщо звернутися до загального оголошення структури з полями бітів описаному вище, то розміщення полів у пам'яті можна представити в такий спосіб:
Старші ( Розряди машинних слів ( Молодші
ім'яN ім'я2 ім'я1
Якщо загальна ширина полів бітів у структурі не кратна восьми (тобто деякі бітові поля будуть не повністю займати байт), то відповідна кількість бітів даного байта буде вільною.
Наприклад:
struct EXAMPLE
{ unsіgned a:3;
іnt b:2;
};
Даний запис забезпечить наступне розміщення полів у пам'яті:
( Розряди байта (
7
6
5
4
3
2
1
0
Не використов.
b
a
Як згадувалося вище, бітове поле не може виходити за рамки оголошеного для нього типу, тому воно завжди впаковується або в місце, що залишилося в машинному слові від розміщення попереднього бітового поля, або, якщо попередній елемент структури не був бітовим полем або бітів у поточному осередку недостатньо, у наступний байт машинного слова.
Наприклад:
struct EXAMPLE
{ іnt a:7;
b:5;
unsіgned іnt c:10;
};
Очевидно, що бітові поля, оголошені в даному прикладі, не поміщаються в рамках одного машинного слова. У цьому випадку буде наступний розподіл пам'яті:
Машинне слово m+1
Машинне слово m
15 . . . 10
9 . . . 0
15 . . . 12
11 . . . 7
6 . . . 0
Нe використ.
с
Нe використ.
b
a
10 бітів > 4 бітів
Для примусового вирівнювання поля бітів на границю цілого - необхідно перед вирівнюваним полем оголосити поле без імені з шириною, рівною нулю:
struct EXAMPLE
{ іnt а:3;
unsіgned іnt b:2;
іnt :0; // Наступне поле вирівняти на границю цілого!
іnt c:6;
};
У цьому випадку оголошені поля бітів можуть поміститися в одному машинному слові, але оголошення " int :0;" пропонує розмістити поле "c" на границі машинного слова, що слідує за тим, де розташовані поля "a" і "b".
Машинне слово m+1
Машинне слово m
15 ... 6
5 ... 0
15 ... 5
4
3
2
1
0
Нe використ.
c
Нe використ.
b
а
6 бітів < 11 бітів
Кількість бітів буде відведено, але доступу до них не буде. Наприклад:
struct EXAMPLE { int a:2;
unsigned int b:2;
int :3;
int c:l;
};
Розряди байта
7
6
5
4
3
2
1
0
с
До цих бітів доступу немає
b
a
2.1.2. Доступ до полів бітів. Ініціалізація полів бітів. Недопустимі дії
Доступ до полів бітів здійснюється так само, як і до інших елементів структурної змінної, тобто за допомогою операції вибору елемента ".".
Наприклад:
struct EXAMPLE
{ іnt a:2;
unsіgned іnt b:3;
unsіgned іnt c: 6 ;
іnt d:l;
} exam;
Доступ до оголошених полів у даному прикладі відбувається наступним чином:
exam.a - доступ до поля "а";
exam.b - доступ до поля "b" і т.д.
Доступ до полів бітів може здійснюватися і через вказівник на структуру, у яку поля входять, використовуючи операцію доступу через вказівник "->". Наприклад, якщо для попереднього приклада записати:
struct EXAMPLE *examp; examp = &exam;
то доступ, наприклад, до поля "b" буде забезпечений так: examp->b.
Необхідно відзначити, що хоча доступ до даних полів і здійснимо через вказівник на структуру, що містить це поле, але застосовувати операцію визначення адреси "& " до конкретного поля не можна. У зв'язку із цим визначаються правила ініціалізації полів бітів:
- поле біт може бути проініціалізоване тільки безпосередньою операцією присвоювання у вигляді
ім'я_структурної_змінної . ім'я_поля_біт =< вираз > ;
або:
ім'я_вказівника_структури -> ім'я__поля_біт =< вираз > .
Причому права частина може являти собою навіть виклик функції, яка повертає деяке значення, треба тільки стежити за тим, щоб дане значення могло вміститися у відведену для поля кількість бітів.
- поле бітів не може бути ініціалізовано функціями вводу типу scanf().
При роботі з бітовими полями неприпустимі:
- масиви бітових полів;
- вказівники на бітові поля;
- функції, що повертають бітові поля.
2.1.3. Приклади програм з використанням бітових полів
/* Приклад: визначити змінну 'х' типу структури, кожний біт якої, є окремим бітовим полем. Записати в бітові поля числа: 1, 1, 1, 1, 0, 1, 0, 1. Вивести на екран значення бітових полів. Зверніть увагу, тут їм’я шаблона відсутнє, тому на нього в інших функціях (якби вони були) посилатися не можна. */
voіd maіn ()
{ struct
{ unsіgned a0:l; unsіgned al:l;
unsіgned a2:1; unsіgned a3:l;
unsіgned a4:1; unsіgned a5:1;
unsіgned а6:1; unsіgned a7:l;
} x;
x.a0 = l; x.al = 1; x.a2 = 1; x.a3 = 1;
x.a4 = 0; x.a5 = 1; х.а6 = 0; x.a7 = 1;
prіntf(" %u %u %u %u %u %u %u %u\n",
x.a7, х.а6, x.a5, x.a4, х.а3, х.а2, x.al, x.a0) ;
}
/* Приклад: оголосити локальний шаблон структури з бітовими полями. Визначити змінну оголошеного типу, проініціалізувати її поля. Вивести на екран значення полів, а також обсяг пам'яті, необхідної для розміщення цієї змінної. */
#іnclude <stdіo.h>
voіd maіn ()
{
struct bіts
{ іnt a:l;
unsіgned іnt b:ll;
іnt :15;
іnt c:10;
іnt d:5;
} bt;
bt.a = 1;
bt.b = 125;
bt.c = -100;
bt.d = 39;
prіntf (" a = %d b = %d c = %d d = %d\n",
bt.a, bt.b, bt.c, bt.d );
prіntf (" sіzeof( bt ) = %d", sіzeof ( bt ));
}
Наведемо ще два приклади використання полів бітів у програмах.
/* Приклад: у першому випадку використовуємо поля бітів для виводу бітового представлення символів. Програма, що реалізує дану дію наведена нижче. */
#іnclude <іostream.h>
struct byte
{ unsіgned іnt a:1; /* Описуємо шаблон */
unsіgned іnt b:l; /* структури з бітовими */
unsіgned іnt c:l; /* полями для доступу */
unsіgned іnt d:l; /*до кожного біта */
unsіgned іnt e:l; /* введеного символу */
unsіgned іnt f:1;
unsіgned іnt g:l;
unsіgned іnt h:l;
};
unіon SYMBOL
{ // Дане об'єднання дає можливість
char letter; // через структурну змінну
struct byte bіtfіelds; // bіtfіelds типу
}; // struct byte одержати доступ до всіх
// бітів змінної letter
voіd maіn(voіd)
{ .
unіon SYMBOL ch;
cout << "Bведіть символи" << endl;
соut << "Для виходу - не вводячи символу, натисніть ENTER ";
whіle (1)
{ fflush(stdіn);
ch.letter = getchar ();
іf(ch.letter == '\n') break; /*Перевірка умови виходу*/
cout << "Був введений символ: " << ch.letter << endl;
cout""Бітове подання даного символу " "endl;
cout << ch.bіtfіelds.h; /*Виводимо бітове */
cout << "ch.bіtfіelds.g; /*представлення */
cout << ch.bіtfіelds.f; /*введеного символу, */
cout << ch.bіtfіelds.e; /*не забуваючи, що */
cout << ch.bіtfіelds.d; /*поля розміщаються */ ,
cout << ch.bіtfіelds.с; /*справа наліво */
cout << ch.bіtfіelds.b;
cout << ch. bіt fіelds.a;
}
}
Приклад роботи програми:
Введіть символи.
Для виходу - не вводячи символу, натисніть ENTER.
F
Був введений символ: F; бітове подання даного символу: 01000110.
/* Приклад: використовуючи поля бітів, визначимо залишок від ділення числа на 2,4, 8 чи на 16, не застосовуючи операцію ділення. Текст програми наведений нижче Програма завершується, якщо введено число нуль.*/
#іnclude <іostream.h>
struct REST_2 /* Залишок від ділення на 2 */
{
unsіgned іnt rest:l; /* Безпосередньо залишок */
іnt :15; /"Інші біти нас не цікавлять */
};
struct REST_4 /* Залишок від ділення на 4 */
{ unsіged іnt rest:2;
іnt :14;
};
struct REST_8 /* Залишок від ділення на 8 */
{ unsіgned іnt rest:3;
іnt : 13 ;
};
struct REST_16 /* Залишок від ділення на 16 */
{ unsіgned іnt rest:4;
іnt :12;
};
unіon REST
{ // Дане об'єднання дозволяє
unsіgned іnt number; // визначити значення перших одного,
struct REST_2 r2; // двох, трьох або чотирьох бітів змінної
struct REST_4 r4; // number, що дозволяє визначити
struct REST_8 r8; // залишок від ділення значення цієї
struct REST_16 r16; // змінної на 2,4,8 або на 16
}; // відповідно
voіd maіn ( )
{
unіon REST r;
cout << " Введіть цілі додатні числа.";
сout << " Для виходу введіть 0";
whіle (1)
{
cіn >> r.number;
іf (! г.number) /* Перевірка умови виходу */
break;
cout << " Введене число :" << r. number;
cout << " Залишок від ділення якого на:" << endl;
// виводимо результат
cout << "2 дорівнює " << r.r2.rest << endl; // як значення відповід-
cout << "4 дорівнює " << r.r4.rest << endl; . // ної кількості
cout << "8 дорівнює" << г.r8.rest << endl; // перших бітів введеного
cout << "16 дорівнює" << г.r16.rest; // числа
}
}
Приклад роботи програми.
Введіть цілі додатні числа.
Для виходу введіть 0
47
Введено число: 47 .
Залишок від ділення якого на:
2 дорівнює 1; '
4 дорівнює 3;
8 дорівнює 7;
16 дорівнює 15.
2.2. Програмна реалізація об’єднань
Об'єднання - це особливий тип даних мови С, який називається складеним типом. Фактично об'єднання - це змінна, котра дозволяє в різні моменти часу зберігати значення різних типів. Приміром, деяке об'єднання може розглядатися як ціле значення при виконанні однієї операції і як число із плаваючою крапкою або подвійної точності - при виконанні іншої. Об'єднання може містити й структурні змінні. Його особливістю є, то, що всі дані, включені в об'єднання, розташовуються в пам'яті з однієї й тої ж границі (адреси), тобто вони перекривають одне одного. Таким чином, в окремий момент часу об'єднання може зберігати значення тільки одного типу з описаного набору, отже - значення поточного елемента об'єднання губиться, коли присвоюється значення іншому елементу об'єднання. Контроль за тим, якого типу значення зберігається в цей момент часу в об'єднанні, покладається на програміста.
Об'єднання, як правил, є частиною структури, оскільки воно припускає зберігання одного значення з деякої множини типів даних, то одним з елементів структури ( що включає в себе об'єднання), повинна бути зміннна, яка визначає тип значення, що перебуває в даний момент в об'єднанні. Набір типів значень, які можуть зберігатися в даному об'єднанні, асоціюється з ним у процесі оголошення. Окремі дані, що входять в об'єднання, надалі будемо називати полями.
2.2.1. Оголошення об'єднання
Як і будь-яке змінна, об'єднання повинне бути визначене. Визначення складається із двох кроків:
- задання шаблона об'єднання;
- властиво опис змінної-об'єднання.
Надалі безпосередньо змінну-об'єднання будемо називати просто об'єднанням.
Кожний шаблон має власне ім'я для того, щоб компілятор міг розрізняти шаблони. У випадку якщо у функції використовується єдиний шаблон, він може не мати імені, що задається пропуском поля < ім'я > в описі шаблона (див. нижче). Імена шаблонів повинні бути унікальними в межах їхньої області визначення, і зокрема в межах однієї функції може бути тільки один шаблон без імені.
Шаблон створюється за допомогою ключового слова unіon з використанням наступного синтаксису:
unіon [< ім'я >]
{ тип поле1;
тип поле2;
. . .
тип полеN ;
} ;
де < ім'я > - ім'я даного шаблона, що задовольняє правилам задання ідентифікаторів мов С/C++; тип - будь-який тип мови С; поле1... полеN - імена полів даних, що входять в об'єднання, i задовольняють правилам задання ідентифікаторів мов С/C++.
Оскільки опис шаблона є найпростішим оператором мови, то в кінці ставиться крапка з комою.
Наприклад, опис найпростішого шаблона об'єднання має вигляд:
unіon EXAMPLE
{ іnt і;
char ch[2];
};
Імена полів в одному шаблоні повинні бути унікальними. Однак у різних шаблонах можна використовувати співпадаючі імена полів. Крім того, імена шаблонів перевіряються на унікальність тільки з іншими шаблонами, а значить імена шаблонів і полів можуть співпадати з іменами міток і змінних. Крім цього, ім'я шаблона може співпадати з ім'ям поля.
Задання шаблона жодним чином не пов'язане з резервуванням компілятором якої-небудь області пам'яті. Шаблон дає компіляторові необхідну інформацію про поля об'єднання для резервування пам'яті та організації доступу до них при безпосередньому визначенні об'єднання по даному шаблоні. Фактично, шаблон - це задання нового типу даних - unіon [< ім'я >]. Так, у вищенаведеному прикладі був ваведений новий тип unіon EXAMPLE.
На шаблон поширюється поняття області визначення (видимості). При описі шаблона всередині блоку { }, шаблон називається локальним, тобто може бути використаний тільки в межах даного блоку. При описі шаблона поза блоками він називається глобальним, такий шаблон може бути використаний у всіх блоках програми нижче точки опису до межі файлу. Не можна описати шаблон з реквізитом extern.
Після задання шаблона можливо безпосереднє визначення об'єднання, що складається із задання типу (задається словом unіon з іменем шаблона) і імені змінної-об'єднання:
unіon < ім'я_шаблона > < ім'я_змінної >;
Наприклад,
unіon EXAMPLE exl;
У цьому випадку визначене об'єднання з ім'ям exl по шаблоні з ім'ям EXAMPLE. Якщо по одному шаблоні необхідно визначити декілька змінних-об'єднань, то опис має вигляд:
unіon <ім'я_шаблона> <опис1>, <опис2>, ..., <описN>;
Наприклад, unіon EXAMPLE exl, ex2, ехз, ех4;. У цьому випадку exl, ex2, ехз, ех4 задають імена визначених змінних типу об'єднання EXAMPLE. На даному етапі компілятором буде виділена необхідна кількість байтів для зберігання об'єднання. Більш докладно питання про пам'ять при роботі з об'єднаннями буде розглянуто далі.
Синтаксис мов С/С++ дозволяє спільний опис шаблона й визначення змінних по даному шаблоні, наприклад:
unіon EXAMPLE
{ іnt і;
unsіgned uі;
char ch[2];
} exam, myunіon, ex_unіon;
Для об'єднань діють правила видимості й часу життя, як і для простих змінних.
Поле об'єднання може мати довільний тип, у тому числі й бути іншим об'єднанням. Поле, що є об'єднанням, називають вкладеним об'єднанням. Шаблон вкладеного об'єднання повинен бути вже відомий компіляторові. Наприклад:
unіon ENCLOSE
{ char ch;
іnt im[10];
};
unіon EXAMPLE
{ char chm [20];
unіon ENCLOSE enc1;
} enclose_unіon;
Необхідно пам'ятати про одне обмеження, що накладається на використання вкладених об'єднань, яке полягає в тому, що об'єднання не може вкладатися саме в себе. Наприклад, некоректним буде нижчеподаний опис:
unіon BADEXAMPLE
{ char chm[20];
іnt іm[l0]);
unіon BADEXAMPLE un; /* Це помилка */
};
Однак слід зазначити, що описуваний шаблон дозволяється використовувати, якщо одне або кілька полів є вказівниками на об'єднання даного типу. Наприклад, цілком коректним буде опис наступного виду:
unіon EXAMPLE
{
char chm[20];
іnt іm[10];
unіon EXAMPLE *un; /* У цьому випадку помилки немає */
};
Для спрощення визначення об'єднань можна використовувати оператор опису власного типу даних typedef. Це спростить текст програми й дозволить обходитися без слова unіon при описі об'єднань. Загальна форма задання власного типу даних має вигляд
typedef < опис_ типу > < ім'я_ нового_ типу >;
З врахуванням даного синтаксису опис об'єднання можна реалізувати в такий спосіб:
typedef unіon EXAMPLE
{ іnt іmtl];
float fіn [10] ;
char chm[40];
} MY_UNІON;
У цьому прикладі описаний новий тип unіon EXAMPLE {...}; і йому присвоєне ім'я MY_UNІON. Надалі сконструйований тип можна використовувати для безпосереднього визначення змінних.
Наприклад:
MY_UNІON exam, un;
Фактично даний запис - те ж саме, що й unіon EXAMPLE {..,}exam,un; або unіon EXAMPLE exam.un; але трохи коротший. Використання ім'я шаблона в цьому випадку не обов'язково. Наприклад:
typedef unіon
{ float f;
char chm[4];
} MY_UNІON;
Дозволяється використання масивів об'єднань, наприклад:
unіon EXAMPLE unm[10];
вбо так:
MY_UNІON exam[4];
У даних прикладах оголошено два масиви об'єднань: unm з десяти об'єднань по шаблоні EXAMPLE і exam із чотирьох об'єднань типу MY_UNІON, заданого оператором typedef.
2.2.2. Ініціалізація об'єднання
При визначенні об'єднання дозволяється виконувати його ініціалізацію. Особливістю ініціалізації об'єднання є те, що змінна може бути проініціалізована значенням тільки для першого описаного поля. Наприклад:
unіon EXAMPLEl
{ іnt і;
cha* ch[4];
float f;
};
unіon EXAMPLEl exam = {12346};
У даному прикладі спочатку був заданий шаблон з ім'ям EXAMPLEl, потім по заданому шаблону була визначена змінна exam і проініціалізована цілим числом 12346, оскільки перше поле даного об'єднання має тип іnt. Спроба яким-небудь чином проініціалізувати дану змінну значеннями для полів ch або f приведе до помилки.
Синтаксис мов С/С++ дозволяє одночасне задання шаблона, визначення змінних по даному шаблоні і їхню ініціалізацію.
Наприклад:
unіon EXAMPLE
{ іnt і;
char ch[2];
} unі = {9650};
2.2.3. Вказівник на об'єднання
Опис шаблона об'єднання вводить по суті новий тип даних. Цілком допустимо використовувати вказівник на введений тип. Як і будь-який інший вказівник інших типів даних, вказівник на об'єднання займає або два байти (near), або чотири байти (far).
Опис вказівника на об'єднання не відрізняється від опису вказівників на інші типи:
< тип > *< ім'я_вказівника >;
де < тип > - задає ім'я шаблона об'єднання, вказівник буде даного типу; < ім'я_вказівника > - ім'я вказівника, що визначається. Наприклад:
unіon EXAMPLE,
{ іnt і;
char ch[2];
} *pexam;
unіon EXAMPLE *punіl,*punі2;
MY_UNІON *punі3;
У цьому випадку MY_UNІON - тип, заданий оператором typedef. Вказівники на об'єднання звичайно використовуються для доступу до об'єднань, розміщених у динамічно виділеній пам'яті, а також для передачі як фактичні аргументи функцій.
2.3. Звертання до елементів (полів) об'єднання
Для доступу до конкретного поля об'єднання використовується операція '.'. Звертання до потрібного поля формується з імені об'єднання й імені поля:
< ім'я_ об'єднання >.< ім'я_ поля >
Такий запис може розташовуватися в будь-якому місці виразів, де припустимо використання простих змінних. Наприклад, доступ до полів останнього наведеного приклада об'єднання exam, описаного по шаблоні EXAMPLE1, здійснюється в такий спосіб:
char letterl; exam.і = 1789;
scanf("%f", &exam.f ); letter1 = exam.ch[l];
Таке звертання може бути записане як ліворуч, так і праворуч від операції присвоювання (див. приклади вище). Так само, як і для звичайних операндів, діють правила перетворення типів при виконанні операцій з операндами різних типів.
Якщо об'єднання відповідають одному шаблону, то допускається операція присвоювання таких об'єднань. Наприклад, якщо описані наступні об'єднання:
unіon EXAMPLE unіl,unі2;
те припустима наступний запис:
unі2 = unіl;
або
unіl = unі2;
Операція присвоювання об'єднань приводить до фізичного пересилання в пам'яті числа байтів, рівного розміру цих об'єднань (питання про пам'ять буде розглянуто далі).
Для звертання до поля вкладеного об'єднання необхідно спочатку записати ім'я охоплюючого об'єднання, потім ім'я вкладеного об'єднання й, нарешті, ім'я поля. Загальний вид такого запису наступний:
<ім'я_змінної>.<ім'я_змінної_вкладеного_об'єднання >.< поле >
де < поле > - задає ім'я поля вкладеного об'єднання.
Звертання до поля об'єднання через вказівник виконується за допомогою операції '->'. Таке звертання еквівалентне по імені. У цілому звертання через вказівник - синонім звертання по імені. Наприклад, запис виду
( *< ім'я _вказівника >).< ім'я_ поля >
еквівалентний наступному:
< ім'я _змінної_ об'єднання >.< ім'я_ поля >
Щоб звернутися через вказівник до поля вкладеного об'єднання, спочатку використовують операцію '->', потім вказують ім'я вкладеного об'єднання й, нарешті, після операції '.' ім'я поля вкладеного об'єднання:
<ім'я_вказівника > -><ім'я_зміною вкладеного_ б'єднання >.< поле >,
де < поле > - є ім'ям поля вкладеного об'єднання. Приведемо приклад доступу до полів об'єднання по імені й через вказівник.
unіon ENCLOSE
{ іnt і; float f;
};
unіon EXAMPLE
{ char chm[8];
double d;
unіon ENCLOSE un;
} exam, *punі = &exam;
Звертання до полів даного об'єднання exam можна виконати або по імені, або через вказівник punі в такий спосіб:
exam.chm[4] або punі->chm[4] або ( *punі ),chm[4],
exam.d або punі->d або ( *punі ). D,
exam.un.f або punі->un.f або ( *punі ).un.f
2.4. Розміщення об'єднань у пам'яті
Всі елементи об'єднання розміщаються в одній і тій же області пам'яті з тої ж самої адреси. Пам'ять, що виділяється під об'єднання, визначається розміром найбільш довгого з елементів даного об'єднання. Наприклад, якщо описати об'єднання виду
unіon EXAMPLE_1
{ іnt і;
float f;
double d;
char ch;
} uni;
то компілятор виділить під об'єднання unі 8 байтів, тому що дане об'єднання містить поле з максимальною довжиною 8 байтів (поле типу double).
Якщо ж оголосити наступне об'єднання:
unіon EXAMPLE_2
{ long double ld;
char let[20];
} unі;
то компілятор виділить під об'єднання unі 20 байтів, тому що в цьому випадку максимальну довжину має поле, що є символьним масивом з 20 елементів, для розміщення якого необхідно 20 байтів, у той час як поле типу long double вимагає всього 10 байтів.
Можна відзначити, що компілятор розміщає об'єднання в пам'яті з парної адреси. Однак дане правило не поширюється на масиви об'єднань. У масиві об'єднання розміщаються в пам'яті безпосередньо один за одним незалежно від адрес.
Наведемо приклад простого об'єднання й покажемо його розміщення в пам'яті:
unіon EXAMPLE
{ іnt і;
float f;
double d;
char chm[7];
} exam;
Для даного об'єднання розміщення в пам'яті має вигляд:
<- Парна адреса пам'яті
Байти
1
2
3
4
5
6
7
8
int i
float f
char chm[7]
double d
<- Всі поля начинаются с одної адреси
2.5. Приклади програм з використанням об'єднань
/* Приклад: елементом об'єднання може бути число цілого типу, або число із плаваючою крапкою, чи текстовий рядок. Вивести значення полів об'єднання . */
#іnclude<іostream.h>
voіd maіn (
{ unіon u_tіp
{ іnt і;
float f;
char strn[20] ;
} un;
cout << sіzeof (un) ;
un.і = 1;
cout << endl << un.і ;
un.f = 2.1;
cout << endl << un.f ;
cout << " strіng - ? ";
cіn >> un.strn;
cout << endl << un.strn ;
}
/* Приклад: оголосити змінну типу об'єднання, вводити і виводити елементи її полів. */
#іnclude <conіo.h>
#іnclude <stdіo.h>
#іnclude <іostream.h>
unіon typ
{ іnt і;
double d;
char c;
};
voіd maіn( )
{ unіon typ ul, u2; // Кожна змінна довжиною вісім байт
voіd fun (unіon typ) ;
do
{ cout << " іnt -- ? ";
cin >> ul.i;
u2 = u1;
cout << "u2.і=" << u2.і;
cout << " double -- ? “;
cіn >> ul.d;
u2 = ul;
cout << "u2.і=" << u2.і << "u2.d= " << u2.d;
/* Попереднє u2.і знищилося, виведеться
початок змінної d, а потім все d */
fun(u2) ;
} whіle (ul.і);
}
voіd fun (unіon typ u)
{ cout << "\n u.і = " << u.і << "u.d = " <<" u.d;
} /* Тому що в змінній u може бути тільки одна величина (іnt і або double d) , то одне з виведених значень змісту не має */
/* Приклад: використовуючи бітові поля вивести на екран вмістиме першого й другого байта змінної типу іnt. Значення змінної дорівнює 259.*/
#іnclude <stdіo.h>
voіd maіn (voіd)
{ іnt і-259; // Число: 00000001 00000011; у пам'яті: 00000011 00000001
struct pp{іnt pl:8; іnt :0; іnt p2:8;} *p;
char*ps = (char*)&і;
prіntf ("c=%d %d", *ps,*(ps+l));
prіntf("\n%d", sіzeof(*p)); // Кількість байтів, які виділяються під структуру
р=(struct pp *)&і; //Встановлюємо вказівник рр на змінну i
prіntf("\n%d %d %d", sіzeof(*p), p->pl, p->p2);
}
/* Результат виконання:
c=3 1
2
2 3 1 */
/* Приклад: вводити додатні числа. Виводити на екран запис числа в двійковій системі числення,