Урок №61. Структуры

  Юрий  | 

  |

  Обновл. 4 Сен 2021  | 

 247760

 ǀ   59 

В программировании есть много случаев, когда может понадобиться больше одной переменной для представления определенного объекта.

Зачем нужны структуры?

Например, для представления самого себя, вы, скорее всего, захотите указать свое имя, день рождения, рост, вес или любую другую информацию:

Но теперь у вас есть 6 отдельных независимых переменных. Если вы захотите передать информацию о себе в функцию, то вам придется передавать каждую переменную по отдельности. Кроме того, если вы захотите хранить информацию о ком-то еще, то вам придется дополнительно объявить еще 6 переменных на каждого человека! Невооруженным глазом видно, что такая реализация не очень эффективна.

К счастью, язык C++ позволяет программистам создавать свои собственные пользовательские типы данных — типы, которые группируют несколько отдельных переменных вместе. Одним из простейших пользовательских типов данных является структура. Структура позволяет сгруппировать переменные разных типов в единое целое.

Объявление и определение структур


Поскольку структуры определяются программистом, то вначале мы должны сообщить компилятору, как она вообще будет выглядеть. Для этого используется ключевое слово struct:

Мы определили структуру с именем Employee. Она содержит 3 переменные:

   id типа short;

   age типа int;

   salary типа double.

Эти переменные, которые являются частью структуры, называются членами структуры (или «полями структуры»). Employee — это простое объявление структуры. Хотя мы и указали компилятору, что она имеет переменные-члены, память под нее сейчас не выделяется. Имена структур принято писать с заглавной буквы, чтобы отличать их от имен переменных.

Предупреждение: Одна из самых простых ошибок в C++ — забыть точку с запятой в конце объявления структуры. Это приведет к ошибке компиляции в следующей строке кода. Современные компиляторы, такие как Visual Studio версии 2010, а также более новых версий, укажут вам, что вы забыли точку с запятой в конце, но более старые компиляторы могут этого и не сделать, из-за чего такую ошибку будет трудно найти. О том, как установить Visual Studio или какую выбрать IDE мы уже говорили на уроке №4.

Чтобы использовать структуру Employee, нам нужно просто объявить переменную типа Employee:

Здесь мы определили переменную типа Employee с именем john. Как и в случае с обычными переменными, определение переменной, типом которой является структура, приведет к выделению памяти для этой переменной.

Объявить можно и несколько переменных одной структуры:

Доступ к членам структур

Когда мы объявляем переменную структуры, например, Employee john, то john ссылается на всю структуру. Для того, чтобы получить доступ к отдельным её членам, используется оператор выбора члена (.). Например, в коде, приведенном ниже, мы используем оператор выбора членов для инициализации каждого члена структуры:

Как и в случае с обычными переменными, переменные-члены структуры не инициализируются автоматически и обычно содержат мусор. Инициализировать их нужно вручную.

В примере, приведенном выше, легко определить, какая переменная относится к структуре John, а какая — к структуре James. Это обеспечивает гораздо более высокий уровень организации, чем в случае с обычными отдельными переменными.

Переменные-члены структуры работают так же, как и простые переменные, поэтому с ними можно выполнять обычные арифметические операции и операции сравнения:

Инициализация структур


Инициализация структур путем присваивания значений каждому члену по порядку — занятие довольно громоздкое (особенно, если этих членов много), поэтому в языке C++ есть более быстрый способ инициализации структур — с помощью списка инициализаторов. Он позволяет инициализировать некоторые или все члены структуры во время объявления переменной типа struct:

В C++11 также можно использовать uniform-инициализацию:

Если в списке инициализаторов не будет одного или нескольких элементов, то им присвоятся значения по умолчанию (обычно, 0). В примере, приведенном выше, члену james.salary присваивается значение по умолчанию 0.0, так как мы сами не предоставили никакого значения во время инициализации.

C++11/14: Инициализация нестатических членов структур

В C++11 добавили возможность присваивать нестатическим (обычным) членам структуры значения по умолчанию. Например:

К сожалению, в C++11 синтаксис инициализации нестатических членов структуры несовместим с синтаксисом списка инициализаторов или uniform-инициализацией. В C++11 следующая программа не скомпилируется:

Следовательно, вам нужно будет решить, хотите ли вы использовать инициализацию нестатических полей или uniform-инициализацию. uniform-инициализация более гибкая, поэтому рекомендуется использовать именно её.

Однако в C++14 это ограничение было снято, и оба варианта можно использовать. Мы еще поговорим детально о статических членах структуры на соответствующем уроке.

Присваивание значений членам структур


До C++11, если бы мы захотели присвоить значения членам структуры, то нам бы пришлось это делать вручную каждому члену по отдельности:

Это боль, особенно когда членов в структуре много. В C++11 вы можете присваивать значения членам структур, используя список инициализаторов:

Структуры и функции

Большим преимуществом использования структур, нежели отдельных переменных, является возможность передать всю структуру в функцию, которая должна работать с её членами:

В примере, приведенном выше, мы передали структуру Employee в функцию printInformation(). Это позволило нам не передавать каждую переменную по отдельности. Более того, если мы когда-либо захотим добавить новых членов в структуру Employee, то нам не придется изменять объявление или вызов функции!

Результат выполнения программы:

ID: 21
Age: 27
Salary: 28.45

ID: 22
Age: 29
Salary: 19.29

Функция также может возвращать структуру (это один из тех немногих случаев, когда функция может возвращать несколько переменных). Например:

Результат выполнения программы:

The point is zero

Вложенные структуры

Одни структуры могут содержать другие структуры. Например:

В этом случае, если бы мы хотели узнать, какая зарплата у CEO (исполнительного директора), то нам бы пришлось использовать оператор выбора членов дважды:

Сначала мы выбираем поле CEO из структуры myCompany, а затем поле salary из структуры Employee.

Вы можете использовать вложенные списки инициализаторов c вложенными структурами:

Размер структур

Как правило, размер структуры — это сумма размеров всех её членов, но не всегда! Например, рассмотрим структуру Employee. На большинстве платформ тип short занимает 2 байта, тип int — 4 байта, а тип double — 8 байт. Следовательно, ожидается, что Employee будет занимать 2 + 4 + 8 = 14 байт. Чтобы узнать точный размер Employee, мы можем воспользоваться оператором sizeof:

Результат выполнения программы (на компьютере автора):

The size of Employee is 16

Оказывается, мы можем сказать только, что размер структуры будет, по крайней мере, не меньше суммы размеров всех её членов. Но он может быть и больше! По соображениям производительности компилятор иногда может добавлять «пробелы/промежутки» в структуры.

В структуре Employee компилятор неявно добавил 2 байта после члена id, увеличивая размер структуры до 16 байтов вместо 14. Описание причины, по которой это происходит, выходит за рамки этого урока, но если вы хотите знать больше, то можете прочитать о выравнивании данных в Википедии.

Доступ к структурам из нескольких файлов

Поскольку объявление структуры не провоцирует выделение памяти, то использовать предварительное объявление для нее вы не сможете. Но есть обходной путь: если вы хотите использовать объявление структуры в нескольких файлах (чтобы иметь возможность создавать переменные этой структуры в нескольких файлах), поместите объявление структуры в заголовочный файл и подключайте этот файл везде, где необходимо использовать структуру.

Переменные типа struct подчиняются тем же правилам, что и обычные переменные. Следовательно, если вы хотите сделать переменную структуры доступной в нескольких файлах, то вы можете использовать ключевое слово extern.

Заключение

Структуры очень важны в языке C++, поскольку их понимание — это первый большой шаг на пути к объектно-ориентированному программированию! Чуть позже мы рассмотрим другой пользовательский тип данных — класс (который является продолжением темы структур).

Тест

Задание №1

У вас есть веб-сайт и вы хотите отслеживать, сколько денег вы зарабатываете в день от размещенной на нем рекламы. Объявите структуру Advertising, которая будет отслеживать:

   сколько объявлений вы показали посетителям (1);

   сколько процентов посетителей нажали на объявления (2);

   сколько вы заработали в среднем за каждое нажатие на объявления (3).

Значения этих трех полей должен вводить пользователь. Передайте структуру Advertising в функцию, которая выведет каждое из этих значений, а затем подсчитает, сколько всего денег вы заработали за день (перемножьте все 3 поля).

Ответ №1

Задание №2

Создайте структуру для хранения дробных чисел. Структура должна иметь 2 члена: целочисленный числитель и целочисленный знаменатель. Объявите две дробные переменные и получите их значения от пользователя. Напишите функцию multiply() (параметрами которой будут эти две переменные), которая будет перемножать эти числа и выводить результат в виде десятичного числа.

Ответ №2

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (574 оценок, среднее: 4,88 из 5)
Загрузка...

Комментариев: 59

  1. Сашуня:

    Первое:

    Второе:

  2. Максим:

    Можно подробне прояснить место

      «… Поскольку объявление структуры не провоцирует выделение памяти, то использовать предварительное объявление для нее вы не сможете. «?

    Непонятно почему под структуру не выделяется память. Тем более, в ответах есть пример с использованием extern.

    1. Ильяс:

      Под объявление структуры не выделяется память, а вот для объекта структуры выделяется. Именно для объекта и используется ключевое слово extern.

  3. Евгений:

    Добрый день!

    Встретил такой синтаксис создания структуры и не совсем понял синтаксис. Первый NULL обнуляет указатель, а *ptr и hints — это псевдонимы.
    Подскажите, пожалуйста, где можно прочитать про такой синтаксис.

    Ошибся. Не псевдонимы, а создаются три переменных с типом info:
    *info1
    *ptr
    hints

  4. Макс:

    Вот так написал 1-ое задание

  5. Ivan:

    А как вышло так, что вот во втором задании нужно создать функцию

    «Напишите функцию multiply() (параметрами которой будут эти две переменные), которая будет перемножать эти числа и выводить результат в виде десятичного числа.» превратилось в

    «умножить числа сами на себя и потом поделить их между собой«, а проще говоря «разделить квадраты двух этих чисел» — до сих поря понять не могу.

    Может объяснит кто логику, или все уже при проверке своего решения просто молча добавили это деление квадратов двух чисел и забили?

    1. Серик Избасканов:

    2. kstnk:

      Логика в том, что нам нужно вывести результат в виде десятичного числа (Примеры десятичного числа — 1, -32, 687.11). У нас есть дробное число ( Примеры дробного числа — 1/2, 43/22, 5/5). Чтобы получить десятичное число из дробного, нужно просто решить дробное число, где «/» — знак деления.

  6. Dima:

    Здравствуйте. Решил и я выложить результаты своего "творчества".

    Задание №1

    Задание №2

  7. Александр:

    Я наверное не до конца понял второе задание, но у меня получилось так.

  8. Саша:

    Можете пожалуйста объяснить структуру:

    1. Ruslan:

      Первая строка — поля структуры.

      Следующие две — конструкторы, т.е. функции для инициализации переменных.

      Остальные — переопределение базовых арифметических операторов, чтобы компактно писать эти операции.

      Например если у нас будет два вектора vec1 и vec2, то

      vec3 = vec1 + vec2

      будет эквивалентно

      vec3.x = vec1.x + vec2.x
      vec3.y = vec1.y + vec2.y

  9. Vitalii:

    Задание №2

  10. Егор:

    Первая задачка

  11. Артём:

    Более доступно, наверное нигде не объясняется! Спасибо за статью)

    1. Фото аватара Юрий:

      Пожалуйста)

  12. Gr1nya:

    урок 61 тестовое задание 1

    урок 61 тестовое задание 2

  13. ОЛЕГ:

    Реально писал программы не смотря ни в ответы ни в комментарии, но почему-то мои программы похожи на другие, которые здесь есть. Наверное из меня не получится выдающегося программиста, так как в моих программах нет оригинальности и своего подхода к решению задач. Увы.

    Задание №1

    main.ccp:

    Задание №2

    main.ccp:

  14. Юрий:

    //задача 1 структуры

    //задача 2 структуры

  15. Руслан:

    Задание № 1

    Задание № 2

  16. Владимир:

    Случайно наткнулся на сайт, подумал: "Ну обычный самоучитель". Но спустя около часа проведения на сайте понял, что это самый лучший самоучитель, всё подробно и понятно описано, приятно читать) Удачи автору)
    Вот, кстати, мои варианты решения задачи:

    1)

    2)

    Не судите строго 🙂

    1. Сергей:

      В 1 задании некорректна математика:
      1) умножение не на процент, а на долевое значение (результат может быть дробный);
      2) стоимость чаще всего десятичная дробь, а в объявлении стоит int

  17. Яна:

    Замечательный урок. Немного второе задание заставило посидеть в написании переменных, т.к. легко можно попутать из-за невнимательности.

    1. Сергей:

      В строке 21 int main(Drob first, Drob second) — неправильные параметры
      int main () — всё в порядке

  18. Furxie Fluke:

    Спасибо огромное за всю эту информацию, которая ещё и так хорошо преподнесена )

    Вот такое написал, в чём постарался ещё и совместить некоторые прошлые уроки, и проверить разные вещи в которых не был уверен

    Так как сам обычно просматриваю все комментарии в поиске дополнительной информации, что бывает очень полезно, решил выложить и свой вариантик :3 Вдруг для кого то он будет полезен

    1. Сергей:

      Ну наворотил то сколько всего!

    2. Ilya:

      Спасибо!!!

      Хорошее закрепление предыдущего материала в одном примере

  19. Дима:

    Огромное спасибо за этот самоучитель,это самое лучшее,что я читал,везде что то опускают,и таким как я очень тяжело,понять,где встречается,что то непонятное,здесь все по порядку не опуская ничего.

  20. Alex_1988:

    "Переменные типа struct подчиняются тем же правилам, что и обычные переменные. Следовательно, если вы хотите сделать переменную структуры доступной в нескольких файлах, то вы можете использовать ключевое слово extern."

    Можно показать пример? У меня не получается подружить структуру со связью extern.

    1. Onium:

      main.cpp

      header.h

      source.cpp

  21. Владимир:

    Решил, что входные данные программка тоже должна принимать:

  22. armus1:

  23. armus1:

  24. Игорь:

    задача №2

  25. Игорь:

  26. Ruslan:

    Немного не подумал, что вы хотели во втором задании просто перемножить два числа, и сделал так:

  27. Алексей:

    Вот честно, не понял я второго задания.
    Тоже ничего сложного по структуре, но написано одно — делать другое.

  28. Алексей:

    Сильный был "завтык" с объявлением структуры в функцию.

    Сделал муторно, потом подправил, но думаю это все равно сложно написано. Задание не сложное, все те же переменные и функции, добавление данных, но вот…

  29. Алексей:

    Структуры сильно напоминают массивы.
    Тут конечно же предоставляются имена для каждой, но все же.

  30. Екатерина:

    Добрый день. Нравятся ваши уроки. Не думаете выпустить печатный вариант?) Легко даётся материал по вашим урокам..и было бы удобно иметь подобного рода настольную книгу)

    1. Фото аватара Юрий:

      Привет, чуть позднее будет .pdf версия всех уроков по С++.

  31. somebox:

    Только посмотрев решение, понял, что нужно указать не дробные, а "дробные" переменные. А то не мог понять, причем тут double. Из-за этого не решил второе задание. Мне кажется, чуть внятнее нужно давать условия задачи.

  32. Дмитрий:

    Решение на первый тест:

    1. Сергей:

  33. Алексей:

    Ответ к заданию номер 2:

  34. Максим:

  35. Эдуард:

    Такой код уместен ко второму заданию? ( Да, знаю что используются "магические числа", а чем их можно заменить, если не использовать цикл?

  36. Torgu:

    Здравствуйте, увидев снятие ограничения на инициализацию структур в C++14, я решил узнать, используется ли у меня C++14. В интернете везде один и тот же способ — написать программу:

    Если программа вывела 199711, то С++98/03, если 201103 — С++11, а если 201300 или 201402, то C++14. У меня вывело 199711, то есть даже C++11 нет. Но тем не менее работает уникальная инициализация, свойственная для C++11. В чем проблема? Как точно определить версию C++? Использую Microsoft Visual Studio Community 2017, все по гайду

  37. master114:

    Мне кажется, что в первом задании Значению количества просмотров лучше не int задавать, а что-нибудь побольше (я long присвоил).
    Ведь обычно количество просмотров рекламы больше, чем 127.
    Иначе будет переполнение и результат мусорный???

    1. Фото аватара Юрий:

      Переменная int, скорее всего, на вашем компьютере занимает 4 байта (у меня 4). Диапазон значений для 4 байтов — от -2 147 483 648 до 2 147 483 647 — всего должно хватать.

  38. Серж:

    Пример программы к заданию номер 2 (проверил ,работает)

    1. Фото аватара Юрий:

      Как вариант да, может быть.

  39. Максим:

    Действительно. Спасибо за Вашу работу. Мои варианты решения.
    Первая:

    Вторая:

    И для себя лично хотелось бы спросить/попросить примеры реальных рабочих задач, какие действительно нужно уметь решать на момент собеседования и на начальных этапах работы.

    1. Фото аватара Юрий:

      У вас как в первом, так и во втором задании используется возврат сразу нескольких значений (через оператор return). Это неверно, оператор return должен возвращать одно значение.

      Насчет задачек — поищите в Интернете или в Ютубе. Предоставить вам реальные задачки, которые используются на собеседовании я не могу, так как сам на собеседования по С++ не ходил.

      1. Максим:

        А почему такое ограничение на return? Код ведь рабочий и значения передаются как положено.

        1. Фото аватара Юрий:

          Например:

          Выведется только одно последнее значение, которое вы введете, так как return может возвращать только одно значение. Когда вам нужно будет использовать возврат значений из функции, как в примере выше, то что тогда будете делать? Есть другие способы возврата сразу нескольких значений из функции, не нарушая при этом правил (смотрите урок 98).

  40. Санжар:

    /*Спасибо за Вашу работу!
    Подскажите пожалуйста, а такой вариант приемлем в первом
    задании?*/

    1. Фото аватара Юрий:

      Пожалуйста 🙂
      Если судить только по результату, то ваш вариант подходит также. Но если с пользовательской точки зрения, то не очень: желательнее всё разбить на функции и описывать действия в программе которые выполняются (например, что значат переменные x, y, z — какие значения пользователю следует вводить).

      1. Санжар:

        Спасибо за ответ, понял.
        Успехов Вам!

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *