У програмуванні є багато випадків, коли може знадобитися більше однієї змінної для представлення певного об’єкта.
- Навіщо потрібні структури?
- Оголошення і визначення структур
- Доступ до членів структур
- Ініціалізація структур
- 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-a Employee james; // створюємо окрему структуру Employee для James-a |
Доступ до членів структур
Коли ми оголошуємо змінну структури, наприклад, 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). Пояснення причини, через яку це відбувається, — виходить за рамки цього туторіалу, але якщо ви хочете знати більше, то можете прочитати про вирівнювання даних у Вікіпедії.
Доступ до структур з декількох файлів
Оскільки оголошення структури не провокує виділення пам’яті, то використовувати попереднє оголошення для неї ви не зможете. Але є обхідний шлях: якщо ви хочете використовувати оголошення структури в декількох файлах (щоб мати можливість створювати змінні цієї структури в декількох файлах), то помістіть оголошення структури в заголовковий файл і #include цей файл всюди, де необхідно використовувати структуру.
Змінні типу 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; } |