У програмуванні є багато випадків, коли може знадобитися більше однієї змінної для представлення певного об’єкта.
- Навіщо потрібні структури?
- Оголошення і визначення структур
- Доступ до членів структур
- Ініціалізація структур
- C++11/14: Ініціалізація нестатичних членів структур
- Присвоювання значень членам структур
- Структури і функції
- Вкладені структури
- Розмір структур
- Доступ до структур з декількох файлів
- Висновки
- Тест
Навіщо потрібні структури?
Наприклад, для представлення самого себе, ви, швидше за все, захочете вказати своє ім’я, день народження, зріст, вагу тощо:
|
1 2 3 4 5 6 |
std::string myName; int myBirthDay; int myBirthMonth; int myBirthYear; int myHeight; int myWeight; |
Тепер у вас є 6 окремих незалежних змінних. Якщо ви захочете передати інформацію про себе в функцію, то вам доведеться передавати кожну змінну окремо. Крім того, якщо ви захочете зберігати інформацію про когось ще, то вам доведеться додатково оголосити ще 6 змінних на кожну людину! Неозброєним оком видно, що така реалізація не дуже ефективна.
На щастя, мова C++ дозволяє програмістам створювати свої власні користувацькі типи даних — типи, які групують декілька окремих змінних разом. Одним з найпростіших користувацьких типів даних є структура. Структура дозволяє згрупувати змінні різних типів даних в єдине ціле.
Оголошення і визначення структур
Оскільки структури визначаються програмістом, то спочатку ми повинні повідомити компілятору, як структура взагалі виглядатиме. Для цього використовується ключове слово struct:
|
1 2 3 4 5 6 |
struct Employee { short id; int age; double salary; }; |
Ми визначили структуру з ім’ям Employee. Вона містить 3 змінні:
id типу short;
age типу int;
salary типу double.
Ці змінні, які є частиною структури, називаються членами структури (або “полями структури”). Employee — це просте оголошення структури. Хоч ми і вказали компілятору, що вона має змінні-члени, пам’ять під неї зараз не виділяється. Імена структур прийнято писати з великої літери, щоб відрізняти їх від імен змінних.
Попередження: Однією з найпростіших помилок в мові C++ є забути вказати крапку з комою в кінці оголошення структури. Це призведе до помилки компіляції в наступному рядку коду. Сучасні компілятори, такі як Visual Studio версії 2010, а також новіші версії, вкажуть вам, що ви забули крапку з комою в кінці, але старіші компілятори можуть цього і не зробити, через що таку помилку буде важко знайти. Про те, як встановити Visual Studio або яку вибрати IDE ми вже говорили на уроці №4.
Щоб використовувати структуру Employee, нам потрібно просто оголосити змінну типу Employee:
|
1 |
Employee john; // ім'я структури Employee починається з великої літери, а змінна john - з маленької |
Тут ми визначили змінну типу Employee з ім’ям john. Як і у випадку зі звичайними змінними, визначення змінної структури призведе до виділення для неї пам’яті.
Оголосити можна і декілька змінних однієї структури:
|
1 2 |
Employee john; // створюємо окрему структуру Employee для John Employee james; // створюємо окрему структуру Employee для James |
Доступ до членів структур
Коли ми оголошуємо змінну структури, наприклад, Employee john, то john посилається на всю структуру. Для того, щоб отримати доступ до окремих її членів, використовується оператор вибору члена (.). Наприклад, в коді, наведеному нижче, ми використовуємо оператор вибору членів для ініціалізації кожного члена структури:
|
1 2 3 4 5 6 7 8 9 |
Employee john; // створюємо окрему структуру Employee для John john.id = 8; // присвоюємо значення члену id структури john john.age = 27; // присвоюємо значення члену age структури john john.salary = 32.17; // присвоюємо значення члену salary структури john Employee james; // створюємо окрему структуру Employee для James james.id = 9; // присвоюємо значення члену id структури james james.age = 30; // присвоюємо значення члену age структури james james.salary = 28.35; // присвоюємо значення члену salary структури james |
Як і у випадку зі звичайними змінними, змінні-члени структури не ініціалізуються автоматично і зазвичай містять сміття. Ініціалізувати їх потрібно вручну.
У вищенаведеному прикладі легко визначити, яка змінна відноситься до структури John, а яка — до структури James. Це забезпечує набагато більш високий рівень організації, ніж у випадку зі звичайними окремими змінними.
Змінні-члени структури працюють так само, як і прості змінні, тому з ними можна виконувати звичайні арифметичні операції і операції порівняння:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
int totalAge = john.age + james.age; if (john.salary > james.salary) cout << "John makes more than James\n"; else if (john.salary < james.salary) cout << "John makes less than James\n"; else cout << "John and James make the same amount\n"; // James отримав підвищення на роботі james.salary += 3.75; // Сьогодні день народження у John ++john.age; // використовуємо пре-інкремент для збільшення віку John на 1 рік |
Ініціалізація структур
Ініціалізація структур шляхом присвоювання значень кожному члену в індивідуальному порядку — заняття досить нудне (особливо, якщо цих членів багато), тому в мові C++ є більш швидкий спосіб ініціалізації структур — за допомогою списку ініціалізаторів. Він дозволяє ініціалізувати деякі або всі члени структури під час оголошення змінної типу struct:
|
1 2 3 4 5 6 7 8 9 |
struct Employee { short id; int age; double salary; }; Employee john = { 5, 27, 45000.0 }; // john.id = 5, john.age = 27, john.salary = 45000.0 Employee james = { 6, 29}; // james.id = 6, james.age = 29, james.salary = 0.0 (ініціалізація за замовчуванням) |
В C++11 також можна використовувати uniform-ініціалізацію:
|
1 2 |
Employee john { 5, 27, 45000.0 }; // john.id = 5, john.age = 27, john.salary = 45000.0 Employee james { 6, 29 }; // james.id = 6, james.age = 29, james.salary = 0.0 (ініціалізація за замовчуванням) |
Якщо в списку ініціалізаторів не буде одного або декількох елементів, то цим елементам присвоїться значення за замовчуванням (зазвичай, 0). У вищенаведеному прикладі члену james.salary присвоюється значення за замовчуванням 0.0, тому що самі ми не надали ніякого значення під час ініціалізації.
C++11/14: Ініціалізація нестатичних членів структур
У C++11 додали можливість присвоювати нестатичним (звичайним) членам структури значення за замовчуванням, наприклад:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> struct Triangle { double length = 2.0; double width = 2.0; }; int main() { Triangle z; // довжина = 2.0, ширина = 2.0 z.length = 3.0; // ви також можете присвоювати членам структури і інші значення return 0; } |
На жаль, в C++11 синтаксис ініціалізації нестатичних членів структури несумісний з синтаксисом списку ініціалізаторів або uniform-ініціалізацією. У C++11 наступна програма НЕ скомпілюється:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> struct Triangle { double length = 2.0; // ініціалізація нестатичних членів double width = 2.0; }; int main() { Triangle z{ 3.0, 3.0 }; // uniform-ініціалізація return 0; } |
Отже, вам потрібно буде вирішити, чи хочете ви використовувати ініціалізацію нестатичних членів чи uniform-ініціалізацію. uniform-ініціалізація більш гнучка, тому я рекомендую використовувати саме її.
Проте в C++14 це обмеження було знято, і обидва варіанти можна використовувати. Ми ще поговоримо детально про статичні члени структур на відповідному уроці.
Присвоювання значень членам структур
До C++11, якби ми захотіли присвоювати значення членам структури, то нам би довелося це робити вручну для кожного члену окремо:
|
1 2 3 4 5 6 7 8 9 10 11 |
struct Employee { short id; int age; double salary; }; Employee john; john.id = 5; john.age = 27; john.salary = 45000.0; |
Це біль, особливо коли членів в структурі багато. У C++11 ви можете присвоювати значення членам структур, використовуючи список ініціалізаторів:
|
1 2 3 4 5 6 7 8 9 |
struct Employee { short id; int age; double salary; }; Employee john; john = { 5, 27, 45000.0 }; // починаючи з C++11 |
Структури і функції
Великою перевагою використання структур замість окремих змінних є можливість передати всю структуру в функцію, яка повинна працювати з її членами:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <iostream> struct Employee { short id; int age; double salary; }; void printInformation(Employee employee) { std::cout << "ID: " << employee.id << "\n"; std::cout << "Age: " << employee.age << "\n"; std::cout << "Salary: " << employee.salary << "\n"; } int main() { Employee john = { 21, 27, 28.45 }; Employee james = { 22, 29, 19.29 }; // Виводимо інформацію про John printInformation(john); std::cout << "\n"; // Виводимо інформацію про James printInformation(james); return 0; } |
У вищенаведеному прикладі ми передали структуру Employee в функцію printInformation(). Це дозволило нам не передавати кожну змінну окремо. Більше того, якщо ми коли-небудь захочемо додати нових членів в структуру Employee, то нам не доведеться змінювати оголошення або виклик функції!
Результат виконання програми:
ID: 21
Age: 27
Salary: 28.45
ID: 22
Age: 29
Salary: 19.29
Функція також може повертати структуру (це один з тих небагатьох випадків, коли функція може повертати декілька змінних), наприклад:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <iostream> struct Point3d { double x; double y; double z; }; Point3d getZeroPoint() { Point3d temp = { 0.0, 0.0, 0.0 }; return temp; } int main() { Point3d zero = getZeroPoint(); if (zero.x == 0.0 && zero.y == 0.0 && zero.z == 0.0) std::cout << "The point is zero\n"; else std::cout << "The point is not zero\n"; return 0; } |
Результат виконання програми:
Вкладені структури
Одні структури можуть містити інші структури, наприклад:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
struct Employee { short id; int age; double salary; }; struct Company { Employee CEO; // Employee - це структура всередині структури Company int numberOfEmployees; }; Company myCompany; |
В цьому випадку, якщо б ми захотіли дізнатися, яка зарплата в CEO (виконавчого директора), то нам довелося б використати оператор вибору членів двічі:
|
1 |
myCompany.CEO.salary |
Спочатку ми вибираємо поле CEO з структури myCompany, а потім вибираємо поле salary з структури Employee.
Ви можете використовувати вкладені списки ініціалізаторів з вкладеними структурами:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
struct Employee { short id; int age; float salary; }; struct Company { Employee CEO; // Employee є структурою всередині структури Company int numberOfEmployees; }; Company myCompany = {{ 3, 35, 55000.0f }, 7 }; |
Розмір структур
Як правило, розмір структури — це сума розмірів всіх її членів, але не завжди!
Наприклад, розглянемо структуру Employee. На більшості платформ тип short займає 2 байти, тип int — 4 байти, а тип double — 8 байт. Отже, очікується, що Employee займатиме 2 + 4 + 8 = 14 байт. Щоб дізнатися точний розмір Employee, ми можемо скористатися оператором sizeof:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> struct Employee { short id; int age; double salary; }; int main() { std::cout << "The size of Employee is " << sizeof(Employee) << "\n"; return 0; } |
Результат виконання програми (на моєму комп’ютері):
The size of Employee is 16
Виявляється, ми можемо сказати тільки, що розмір структури буде, принаймні, не менше суми розмірів всіх її членів. Але він може бути і більше! З міркувань продуктивності компілятор іноді може додавати “пробіли” в структури.
У структурі Employee компілятор неявно додав 2 байти після члену id, збільшуючи розмір структури до 16 байтів (замість 14). Пояснення причини, через яку це відбувається, — виходить за рамки цього туторіалу, але якщо ви хочете знати більше, то можете прочитати про вирівнювання даних у Вікіпедії.
Доступ до структур з декількох файлів
Оскільки оголошення структури не спричинює виділення пам’яті, то використовувати попереднє оголошення для неї ви не зможете. Але є обхідний шлях: якщо ви хочете використовувати оголошення структури в декількох файлах (щоб мати можливість створювати змінні цієї структури в декількох файлах), то помістіть оголошення структури в заголовковий файл і підключайте цей файл всюди, де необхідно використовувати структуру.
Змінні типу struct слідують тим же правилам, що і звичайні змінні. Отже, якщо ви хочете зробити змінну структури доступною в декількох файлах, то ви можете використати ключове слово extern.
Висновки
Структури дуже важливі в мові C++, оскільки їх розуміння — це перший великий крок до об’єктно-орієнтованого програмування! Трішки пізніше ми розглянемо інший користувацький тип даних — клас (який є продовженням теми структур).
Тест
Завдання №1
У вас є веб-сайт і ви хочете відстежувати, скільки грошей ви заробляєте в день від розміщеної на ньому реклами. Оголосіть структуру Advertising, яка буде відслідковувати:
скільки оголошень ви показали відвідувачам (1);
скільки відсотків відвідувачів натиснули на оголошення (2);
скільки ви заробили в середньому за кожен клік по оголошенню (3).
Значення цих трьох полів повинен вводити користувач. Передайте структуру Advertising в функцію, яка виведе кожне з цих значень, а потім підрахує, скільки всього грошей ви заробили за день (перемножте всі 3 поля).
Відповідь №1
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <iostream> // Спочатку оголошуємо структуру Advertising struct Advertising { int adsShown; double clickThroughRatePercentage; double averageEarningsPerClick; }; void printAdvertising(Advertising ad) { using namespace std; cout << "Number of ads shown: " << ad.adsShown << endl; cout << "Click through rate: " << ad.clickThroughRatePercentage << endl; cout << "Average earnings per click: $" << ad.averageEarningsPerClick << endl; // Ми ділимо ad.clickThroughRatePercentage на 100, тому що користувач вказує відсотки, а не готове число cout << "Total Earnings: $" << (ad.adsShown * ad.clickThroughRatePercentage / 100 * ad.averageEarningsPerClick) << endl; } int main() { using namespace std; // Оголошуємо змінну структури Advertising Advertising ad; cout << "How many ads were shown today? "; cin >> ad.adsShown; cout << "What percentage of users clicked on the ads? "; cin >> ad.clickThroughRatePercentage; cout << "What was the average earnings per click? "; cin >> ad.averageEarningsPerClick; printAdvertising(ad); return 0; } |
Завдання №2
Створіть структуру для зберігання дробових чисел. Структура повинна мати 2 члени: цілочисельний чисельник і цілочисельний знаменник. Оголосіть дві дробові змінні і отримайте їх значення від користувача. Напишіть функцію multiply() (параметрами якої будуть ці дві змінні), яка перемножить ці числа і виведе результат у вигляді десяткового числа.
Відповідь №2
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <iostream> struct Drob { int chislitel; int znamenatel; }; void multiply(Drob d1, Drob d2) { using namespace std; // Не забуваємо про static_cast, інакше компілятор виконає цілочисельне ділення! cout << static_cast<float>(d1.chislitel* d2.chislitel) / (d1.znamenatel* d2.znamenatel); } int main() { using namespace std; // Визначаємо першу дробову змінну Drob d1; cout << "Input the first chislitel: "; cin >> d1.chislitel; cout << "Input the first znamenatel: "; cin >> d1.znamenatel; // Визначаємо другу дробову змінну Drob d2; cout << "Input the second chislitel: "; cin >> d2.chislitel; cout << "Input the second znamenatel: "; cin >> d2.znamenatel; multiply(d1, d2); return 0; } |

Дякую за уроки українською.
Що до розміру структур… Якщо в стандарті c99 останнім членом структури вказати масив без розміру (вказівник на масив).
При цьому фунцкція sizeof обраховує розмір такої структури БЕЗ врахування цього останнього масиву. У функціях динамічного виділення памяті для таких структур потрібно до розміру структури додати необхідний нам розмір масиву.