Теоретичні відомості#
Програміст під час написання програми мовою С може створювати дані п’ятьох типів:
структуру (structure);
об’єднання (union);
перераховуваний тип (enumeration);
поля бітів (bit fields);
нове ім’я (псевдонім) для вже існуючого типу (за допомогою оператора
typedef
).
Структура#
У розробленні програм важливим є вибір ефективного способу подання інформації. У багатьох випадках недостатньо оголосити просту змінну чи масив: потрібна гнучкіша форма подання. Таким елементом може бути структура, яка дає змогу включати в себе дані різних типів, а також інші структури. Структуру задають у мові С за допомогою ключового слова struct
, за яким слідує її ім’я.
Структура об’єднує декілька змінних, можливо, різного типу. Змінні, об’єднані структурою, називають її членами, елементами чи полями. Приклад визначення структури:
struct student
{
char name[30];
int year;
char group[3];
int scholarship;
};
Оголошення структури є оператором, тому після нього ставлять крапку з комою. При цьому потрібно розуміти, що на даному етапі жодної змінної не оголошено, оскільки не відбулося виділення пам’яті під змінні. У прикладі під іменем student
задано вид структури, інакше кажучи, її шаблон, і визначено новий тип struct student
. Щоб оголосити конкретні змінні типу struct student
, можна записати:
struct student stud1, stud2;
Тепер оголошено дві змінні — stud1
і stud2
. Компілятор автоматично виділить під них безперервну ділянку пам’яті комп’ютера, що дає можливість працювати зі структурою як із єдиним об’єктом даних, який має в даному прикладі чотири поля: name
, year
, group
, scholarship
. Створення шаблона структури й оголошення змінних можна здійснювати й в одному операторі:
struct student
{
char name[30];
int year;
char group[3];
int scholarship;
} stud1, stud2;
У цьому випадку одночасно задається структура з іменем student та оголошуються змінні stud1
і stud2
.
Доступ до конкретного елемента (поля) структури здійснюють за допомогою операції .
(«крапка», dot), наприклад:
strcpy(stud1.name, "Кузина А. А.");
Якщо потрібно надрукувати вміст третього поля змінної stud2
структури student
, то треба записати:
printf("%s", stud2.group);
Структури, як і змінні інших типів, можна об’єднувати в масиви структур. Щоб оголосити масив структур, потрібно спочатку задати шаблон структури, а потім оголосити сам масив:
struct student stud1year[200];
Цей оператор створить у пам’яті 200 змінних типу структури з шаблоном student
та іменами stud1year[0]
, stud1year[1]
і т.д. Для доступу до поля year
, наприклад, 35-го елемента масиву потрібно використати stud1year[34].year
.
Якщо оголошено дві змінні типу структури з одним шаблоном, можна виконувати присвоювання: stud1 = stud2
. При цьому відбудеться побітове копіювання кожного поля однієї змінної у відповідне поле іншої. Не можна використовувати операцію присвоювання змінних типу структури, шаблони яких описано під різними іменами, нехай навіть зовсім ідентично. Тим не менше, у такому випадку можливе присвоювання для окремих полів однакового типу.
Змінна типу структури може бути глобальною, локальною змінною та формальним параметром. Структуру чи її поле можна використовувати як параметр функції, наприклад, func1(right.a)
або func2(&left.b)
. Варто зауважити, що &
ставлять перед іменем структури, а не перед іменем поля.
Як формальний параметр можна передати за значенням усю структуру. Також можна створити вказівник на структуру й передавати аргумент типу структури за посиланням. Оголосити вказівник на структуру можна так:
struct stru* adr_pointer;
де adr_pointer
— змінна-вказівник на структуру struct stru
.
Якщо структуру передають за значенням, то всі поля структури заносять у стек. Якщо структура проста й містить мало елементів, то це не так істотно. Якщо ж структура як своє поле містить масив, то стек може переповнитися. Натомість, у випадку передачі за посиланням у стек заносять тільки адресу структури, її копіювання не відбувається. Додатково з’являється можливість змінювати вміст полів структури.
Розгляньмо відповідний приклад:
struct complex
{
float x;
float y;
} c1, c2;
struct complex* a;
a = &c1;
У цьому прикладі вказівнику а присвоюється адреса змінної с1
. Одержати значення поля х
змінної с1
можна так: (*a).x
.
Використання вказівників на структуру зустрічається досить часто, наприклад, у програмуванні завдань зі створення й опрацювання динамічних зв’язаних структур даних (черги, стеки, дерева тощо). Тому, крім способу одержати значення поля структури, використовуючи (*a).x
, можна використовувати інший спосіб — використовуючи спеціальну операцію мови С ->
(«стрілка», arrow).
Операцію «стрілка» вживають замість операції «крапка», коли потрібно використати значення поля структури з застосуванням змінної вказівника. Тоді, замість (*a).x
можна використовувати a->x
. Цей спосіб застосовують найчастіше.
Як поля структури можна використовувати масиви, структури й масиви структур, наприклад:
struct addr
{
char city[34];
char street[76];
int house;
};
struct fulladdr
{
struct addr address;
int room;
char name[48];
} a, b;
де addr
— шаблон структури, визначений перед оголошенням структури fulladdr
та оголошенням змінної а типу структури fulladdr
. Для присвоєння значення полю house
структури address
змінної а потрібно використати a.adress.house = 234
.
Доступ до окремих бітів#
На відміну від багатьох інших мов програмування, мова С забезпечує доступ до одного чи декількох бітів у байті чи слові. У конкретних задачах часто буває потрібно, щоб деяка змінна набувала тільки двох значень. Для цього достатньо використовувати тільки один біт пам’яті. За своїм змістовним сенсом така змінна є деякою ознакою (прапорцем).
Один із методів, убудованих у мову C, який дає змогу одержати доступ до біта, — це поля бітів. Поля бітів — це спеціальний тип членів структури, у якому визначено, зі скількох бітів складається кожне поле. Основна форма оголошення подібної структури така:
struct <ім’я_структури>
{
<тип> <ім’я1> : <довжина в бітах>;
<тип> <ім’я2> : <довжина в бітах>;
...
<тип> <ім’яN> : <довжина в бітах>;
};
де
<тип>
може бути одним із таких:int
,unsigned
,signed
;будь-яке з
<імен>
може бути пропущено, і тоді відповідна кількість бітів не використується (пропускається).
Довжина структури завжди кратна 8. Так, якщо вказати
struct onebit
{
unsigned one_bit : 1;
} obj;
то для змінної obj
буде виділено 8 бітів, але використовуватиметься тільки перший. У структурі може бути змішано звичайні змінні та поля бітів.
Об’єднання#
У мові С визначено ще один тип для розміщення в пам’яті декількох змінних різного типу — об’єднання. Цей засіб дає можливість зберігати в окремі моменти часу в одному й тому самому місці пам’яті дані різних типів. Об’єднання дають змогу працювати в одній і тій самій області пам’яті з даними різного вигляду, не вносячи до програми інформації, залежної від машини.
Об’єднання описують так само, як і структури, але замість ключового слова struct
використовують ключове слово union
. Як і для структур, можна визначити шаблон об’єднання з іменем типу й задати в цьому ж операторі одну чи декілька змінних об’єднання. Або ж опис змінних можна відокремити від визначення шаблону.
Розгляньмо приклад шаблону об’єднання з іменем типу u
:
union u
{
int i;
char ch;
long int l;
};
Це оголошення задає шаблон об’єднання. Можна оголосити змінні:
union u alfa, beta;
На відміну від структури, для змінної типу union місця в пам’яті виділяється рівно стільки, скільки потрібно полю об’єднання, що має найбільший розмір у байтах. У наведеному вище прикладі під змінну alfa
буде виділено 4 байти пам’яті, оскільки поле i вимагає 2 байти, поле ch
— 1 байт, поле l
— 4 байти. Усі змінні розташовуватимуться в тому ж місці пам’яті. Програміст повинен самостійно стежити за типом даних, який записується в об’єднання в даний момент.
Синтаксис використання полів об’єднання такий самий, як і для структури, наприклад, u.ch = 'N'
. Для об’єднання також дозволено операцію ->
, якщо звернення до об’єднання здійснюють за допомогою вказівника. Для змінних типу об’єднання також можна використовувати операції взяття адреси та одержання елемента через указівник.
Перерахування#
Перерахування — це конструйований тип даних, під час опису якого задають ім’я типу даних і значення, яких можуть набувати змінні цього типу.
Опис перерахованого типу даних здійснюють оператором виду:
enum <ім’я_типу> {<список_значень>} <список змінних>;
де
<ім’я_типу>
задають ідентифікатором С на розсуд програміста;<список_значень>
задають у вигляді:<елемент1>, <елемент2>, ..., <елементN>
, де будь-який із елементів є або<ім’я>
, або<ім’я> = <константний_вираз>
. Як<ім’я>
використовують ідентифікатор мовою С;<список_змінних>
— імена змінних типу перерахування, відокремлені один від одного комами.
Семантично опис перерахування задає ім’я типу, визначає іменовані константи та імена змінних, які можуть набувати значень іменованих констант. Значення кожної іменованої константи зі списку значень є ціле число. Вирази з константами, якщо їх використовують, повинні бути цілими та можуть бути від’ємними. Якщо вирази з константами відсутні, то за замовчуванням першому імені списку значень відповідає 0, другому — 1 і т.д.
Використання в програмі іменованої константи рівнозначно використанню її значення. Явна ініціалізація іменованої константи константним виразом перевизначає послідовність значень, заданих за замовчуванням. Ім’я, наступне за перевизначеним іменем, набуває значення, збільшеного на 1, якщо тільки його значення не задано явно іншою величиною.
Приклад визначення перерахованого типу і змінної даного типу:
enum season {win, spr, sum, aut};
enum season s;
Ключем до розуміння сутності перерахованого типу є те, що кожне з імен win
, spr
, sum
, aut
є цілою величиною. Якщо ці величини не визначено інакше, то за замовчуванням вони дорівнюють 0, 1, 2 і 3, відповідно.
Оператор printf("%d%d", win, aut);
видасть на екран числа 0 3
. Під час оголошення типу можна одному або декільком символам привласнити інші значення, наприклад:
enum value { one = 1, two, three, ten = 10, twenty = 20, next};
Якщо тепер надрукувати значення printf("%d%d%d%d%d\n", one, two, ten, twenty, next);
, то на екрані з’являться числа 1 2 10 20 21
, тобто кожний наступний символ збільшується на 1 порівняно з попереднім, якщо немає іншого присвоювання.
Список значень може містити повторювані значення імен, але самі імена повинні бути різні. Список змінних в описі перерахованого типу можна опустити. У цьому випадку опис задає лише тип перерахування, що має задане ім’я та список допустимих значень. Змінні в цьому випадку повинно бути описано за допомогою певного перерахованого типу.
Цей спосіб опису використовує ім’я типу перерахування (яке іноді зивають тегом (ярликом) перерахування), яке посилається на раніше описаний тип і використовує його список перерахування, наприклад:
enum day { saturday, sunday = 0, monday, tuesday, wednesday, thursday, friday } workday;
enum day nextday;
enum day today = monday;
day vacancy;
У першому випадку визначено тип перерахування day
, оголошено змінну перерахування workday
. Іменовані константи одержують такі значення: saturday
— 0 (за замовчуванням), sunday
— 0 (явно), інші імена отримують значення від 1 до 5.
У другому випадку оголошено змінну nextday
типу enum day
. Оскільки тип enum day
було оголошено раніше, то достатньо лише на нього послатися. У третьому випадку оголошено змінну today
типу enum day
, і її ініціалізовано значенням monday
зі списку значень.
Можна оголошувати й масиви перерахованого типу, наприклад:
enum day week[6];
Зі змінними перераховуваного типу можна проводити такі операції:
присвоїти змінну типу enum іншій змінній того ж типу;
провести порівняння на рівність або нерівність;
арифметичні операції з константами типу
enum(i = win + aut)
.
Не можна до змінних типу enum
використовувати арифметичні операції та операції ++
і --
.
Основна причина використання перераховуваного типу — поліпшення читовності програм.
Перейменування типів#
Мова С дає змогу дати нову назву вже існуючому типу даних. Для цього використовують ключове слово typedef
. При цьому створюється новий тип даних. Наприклад:
typedef char SYMBOL;
typedef unsigned UNSIGN;
typedef float real;
Досить часто використовують оператор typedef
із застосуванням структур:
typedef struct st_tag
{
char name[40];
int year;
char group[5];
int scholarship;
} STUDENT;
Тепер для визначення змінної можна використовувати як struct st_tag avar
, так і STUDENT avar
.