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

  Юрий  | 

  Обновл. 14 Сен 2020  | 

 99427

 ǀ   36 

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

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

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

Но теперь у вас есть 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).

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

Ответ №1

Задание №2

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

Ответ №2

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

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

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

  1. Аватар Руслан:

    Задание № 1

    Задание № 2

  2. Аватар Владимир:

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

    1)

    2)

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

  3. Аватар Яна:

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

  4. Аватар Furxie Fluke:

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

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

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

    1. Аватар Сергей:

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

  5. Аватар Дима:

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

  6. Аватар Alex_1988:

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

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

    1. Аватар Onium:

      main.cpp

      header.h

      source.cpp

  7. Аватар Владимир:

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

  8. Аватар armus1:

  9. Аватар armus1:

  10. Аватар Игорь:

    задача №2

  11. Аватар Игорь:

  12. Аватар Ruslan:

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

  13. Аватар Алексей:

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

  14. Аватар Алексей:

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

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

  15. Аватар Алексей:

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

  16. Аватар Екатерина:

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

    1. Юрий Юрий:

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

  17. Аватар somebox:

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

  18. Аватар Дмитрий:

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

  19. Аватар Алексей:

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

  20. Аватар Максим:

  21. Аватар Эдуард:

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

  22. Аватар 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, все по гайду

  23. Аватар master114:

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

    1. Юрий Юрий:

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

  24. Аватар Серж:

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

    1. Юрий Юрий:

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

  25. Аватар Максим:

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

    Вторая:

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

    1. Юрий Юрий:

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

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

      1. Аватар Максим:

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

        1. Юрий Юрий:

          Например:

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

  26. Аватар Санжар:

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

    1. Юрий Юрий:

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

      1. Аватар Санжар:

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

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

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