В программировании есть много случаев, когда может понадобиться больше одной переменной для представления определенного объекта.
- Зачем нужны структуры?
- Объявление и определение структур
- Доступ к членам структур
- Инициализация структур
- 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
.
Вы можете использовать вложенные списки инициализаторов c вложенными структурами:
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 40 |
#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; } |
Добрый день!
Встретил такой синтаксис создания структуры и не совсем понял синтаксис. Первый NULL обнуляет указатель, а *ptr и hints — это псевдонимы.
Подскажите, пожалуйста, где можно прочитать про такой синтаксис.
Ошибся. Не псевдонимы, а создаются три переменных с типом info:
*info1
*ptr
hints
Вот так написал 1-ое задание
А как вышло так, что вот во втором задании нужно создать функцию
«Напишите функцию multiply() (параметрами которой будут эти две переменные), которая будет перемножать эти числа и выводить результат в виде десятичного числа.» превратилось в
«умножить числа сами на себя и потом поделить их между собой«, а проще говоря «разделить квадраты двух этих чисел» — до сих поря понять не могу.
Может объяснит кто логику, или все уже при проверке своего решения просто молча добавили это деление квадратов двух чисел и забили?
Логика в том, что нам нужно вывести результат в виде десятичного числа (Примеры десятичного числа — 1, -32, 687.11). У нас есть дробное число ( Примеры дробного числа — 1/2, 43/22, 5/5). Чтобы получить десятичное число из дробного, нужно просто решить дробное число, где «/» — знак деления.
Здравствуйте. Решил и я выложить результаты своего "творчества".
Задание №1
Задание №2
Я наверное не до конца понял второе задание, но у меня получилось так.
Можете пожалуйста объяснить структуру:
Первая строка — поля структуры.
Следующие две — конструкторы, т.е. функции для инициализации переменных.
Остальные — переопределение базовых арифметических операторов, чтобы компактно писать эти операции.
Например если у нас будет два вектора vec1 и vec2, то
vec3 = vec1 + vec2
будет эквивалентно
vec3.x = vec1.x + vec2.x
vec3.y = vec1.y + vec2.y
Задание №2
Первая задачка
Более доступно, наверное нигде не объясняется! Спасибо за статью)
Пожалуйста)
урок 61 тестовое задание 1
урок 61 тестовое задание 2
Реально писал программы не смотря ни в ответы ни в комментарии, но почему-то мои программы похожи на другие, которые здесь есть. Наверное из меня не получится выдающегося программиста, так как в моих программах нет оригинальности и своего подхода к решению задач. Увы.
Задание №1
main.ccp:
Задание №2
main.ccp:
//задача 1 структуры
//задача 2 структуры
Задание № 1
Задание № 2
Случайно наткнулся на сайт, подумал: "Ну обычный самоучитель". Но спустя около часа проведения на сайте понял, что это самый лучший самоучитель, всё подробно и понятно описано, приятно читать) Удачи автору)
Вот, кстати, мои варианты решения задачи:
1)
2)
Не судите строго 🙂
В 1 задании некорректна математика:
1) умножение не на процент, а на долевое значение (результат может быть дробный);
2) стоимость чаще всего десятичная дробь, а в объявлении стоит int
Замечательный урок. Немного второе задание заставило посидеть в написании переменных, т.к. легко можно попутать из-за невнимательности.
В строке 21 int main(Drob first, Drob second) — неправильные параметры
int main () — всё в порядке
Спасибо огромное за всю эту информацию, которая ещё и так хорошо преподнесена )
Вот такое написал, в чём постарался ещё и совместить некоторые прошлые уроки, и проверить разные вещи в которых не был уверен
Так как сам обычно просматриваю все комментарии в поиске дополнительной информации, что бывает очень полезно, решил выложить и свой вариантик :3 Вдруг для кого то он будет полезен
Ну наворотил то сколько всего!
Спасибо!!!
Хорошее закрепление предыдущего материала в одном примере
Огромное спасибо за этот самоучитель,это самое лучшее,что я читал,везде что то опускают,и таким как я очень тяжело,понять,где встречается,что то непонятное,здесь все по порядку не опуская ничего.
"Переменные типа struct подчиняются тем же правилам, что и обычные переменные. Следовательно, если вы хотите сделать переменную структуры доступной в нескольких файлах, то вы можете использовать ключевое слово extern."
Можно показать пример? У меня не получается подружить структуру со связью extern.
main.cpp
header.h
source.cpp
Решил, что входные данные программка тоже должна принимать:
задача №2
Немного не подумал, что вы хотели во втором задании просто перемножить два числа, и сделал так:
Вот честно, не понял я второго задания.
Тоже ничего сложного по структуре, но написано одно — делать другое.
Сильный был "завтык" с объявлением структуры в функцию.
Сделал муторно, потом подправил, но думаю это все равно сложно написано. Задание не сложное, все те же переменные и функции, добавление данных, но вот…
Структуры сильно напоминают массивы.
Тут конечно же предоставляются имена для каждой, но все же.
Добрый день. Нравятся ваши уроки. Не думаете выпустить печатный вариант?) Легко даётся материал по вашим урокам..и было бы удобно иметь подобного рода настольную книгу)
Привет, чуть позднее будет .pdf версия всех уроков по С++.
Только посмотрев решение, понял, что нужно указать не дробные, а "дробные" переменные. А то не мог понять, причем тут double. Из-за этого не решил второе задание. Мне кажется, чуть внятнее нужно давать условия задачи.
Решение на первый тест:
Ответ к заданию номер 2: