Урок №117. Список инициализации членов класса

  Юрий  | 

  |

  Обновл. 24 Янв 2022  | 

 109607

 ǀ   16 

На этом уроке мы рассмотрим, как инициализировать переменные-члены класса с помощью списка инициализации в языке С++, а также особенности и нюансы, которые при этом могут возникнуть.

Списки инициализации членов класса

На предыдущем уроке мы инициализировали члены нашего класса в конструкторе через оператор присваивания:

Сначала создаются m_value1, m_value2 и m_value3. Затем выполняется тело конструктора, где этим переменным присваиваются значения. Аналогичен код в не объектно-ориентированном C++:

Хотя в плане синтаксиса языка C++ вопросов никаких нет — всё корректно, но более эффективно — использовать инициализацию, а не присваивание после объявления.

Как мы уже знаем из предыдущих уроков, некоторые типы данных (например, константы и ссылки) должны быть инициализированы сразу. Рассмотрим следующий пример:

Аналогичен код в не объектно-ориентированном C++:

Для решения этой проблемы в C++ добавили метод инициализации переменных-членов класса через список инициализации членов, вместо присваивания им значений после объявления. Не путайте этот список с аналогичным списком инициализаторов, который используется для инициализации массивов.

Из урока №28 мы уже знаем, что инициализировать переменные можно тремя способами: через копирующую инициализацию, прямую инициализацию или uniform-инициализацию.

Использование списка инициализации почти идентично выполнению прямой инициализации (или uniform-инициализации в C++11).

Чтобы было понятнее, рассмотрим пример. Вот код с присваиванием значений переменным-членам класса в конструкторе:

Теперь давайте перепишем этот код, но уже с использованием списка инициализации:



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

Values(3, 4.5, d)

Список инициализации членов находится сразу же после параметров конструктора. Он начинается с двоеточия (:), а затем значение для каждой переменной указывается в круглых скобках. Больше не нужно выполнять операции присваивания в теле конструктора. Также обратите внимание, что список инициализации членов не заканчивается точкой с запятой.

Можно также добавить возможность caller-у передавать значения для инициализации:

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

Values(3, 4.5, d)

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

Это работает, поскольку нам разрешено инициализировать константные переменные (но не присваивать им значения после объявления!).

Правило: Используйте списки инициализации членов, вместо операций присваивания, для инициализации переменных-членов вашего класса.

uniform-инициализация в C++11


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

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

Правило: Используйте uniform-инициализацию вместо прямой инициализации в C++11.

Инициализация массивов в классе

Рассмотрим класс с массивом в качестве переменной-члена:

До C++11 мы могли только обнулить массив через список инициализации:

Однако в C++11 вы можете полностью инициализировать массив, используя uniform-инициализацию:

Инициализация переменных-членов, которые являются классами


Список инициализации членов также может использоваться для инициализации членов, которые являются классами:

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

A 6
B 7

При создании переменной b вызывается конструктор B(int) со значением 7. До того, как тело конструктора выполнится, инициализируется m_a, вызывая конструктор A(int) со значением 6. Таким образом, выведется A 6. Затем управление возвратится обратно к конструктору B(), и тогда уже он выполнится и выведется B 7.

Использование списков инициализации

Если список инициализации помещается на той же строке, что и имя конструктора, то лучше всё разместить в одной строке:

Если список инициализации членов не помещается в строке с именем конструктора, то на следующей строке (используя перенос) инициализаторы должны быть с отступом:

Если все инициализаторы не помещаются на одной строке, то вы можете выделить для каждого инициализатора отдельную строку:

Порядок выполнения в списке инициализации


Удивительно, но переменные в списке инициализации не инициализируются в том порядке, в котором они указаны. Вместо этого они инициализируются в том порядке, в котором объявлены в классе, поэтому следует соблюдать следующие рекомендации:

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

   Инициализируйте переменные в списке инициализации в том порядке, в котором они объявлены в классе.

Заключение

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

Тест

Напишите класс с именем RGBA, который содержит 4 переменные-члены типа std::uint8_t (подключите заголовочный файл cstdint для доступа к типу std::uint8_t):

   m_red;

   m_green;

   m_blue;

   m_alpha.

Присвойте 0 в качестве значения по умолчанию для m_red, m_green и m_blue, и 255 для m_alpha. Создайте конструктор со списком инициализации членов, который позволит пользователю передавать значения для m_red,m_green, m_blue и m_alpha. Напишите функцию print(), которая будет выводить значения переменных-членов.

Подсказка: Если функция print() работает некорректно, то убедитесь, что вы конвертировали std::uint8_t в int.

Следующий код функции main():

Должен выдавать следующий результат:

r=0 g=135 b=135 a=255

Ответ

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

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

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

  1. Владислав:

    Почему std::cout некорректно выводит std::uint8_t? С чем это связано?

    1. ку:

      Читать до конца надо)(а если честно, то из-за не корректного преобразования)

    2. Павел:

      Т.е. программа, приведенная выше, обрабатывает myint как переменную типа char.

      https://ravesli.com/urok-32-fiksirovannyj-razmer-integers-spor-naschet-unsigned/#toc-3

  2. Достон:

    Спасибо за вашу работу ! Помогает в обучении !

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

      Пожалуйста)

  3. Андрей:

    Немножко не понравилось слово "обнуляем" массив. Скользкое слово, и его можно понимать по-разному. Я сначала думал, что "обнулить", это значит ликвидировать массив, сделать его под ноль. А потом возникла версия, что обнулить — это заполнить массив нулями. Семь членов — семь нулей…

    1. Алексей:

      Да, есть такое. Режет слух или бьет по психологии.
      Тут думаю, что можно было и без этого "обнулить" обойтись. Объявили массив на 7 и хватит.

  4. somebox:

    А зачем в тестовом задании используется тип переменной std::uint8_t? Просто так?

    1. Dmitrii:

      Я так понял, что uint8_t может принять максимум 2^8 = 256 байт информации. Каналы RGB как раз находятся в диапазоне 0-255, поэтому использовать unsigned int нет смысла (только лишняя потеря памяти), так как значение все равно не будет больше 255.

      1. Евгений:

        uint8_t — это беззнаковая целая переменная длиной 1 байт (8 бит). Соответственно, может принимать значения 0..255

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

    Спасибо Юрий! Тема даётся очень трудно. Для меня это действительно сложно. Но я вам признателен за вашу большую работу.

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

      Пожалуйста. Если не получается — не зацикливайтесь, продолжайте изучение со следующих уроков.

  6. Ануар:

    спасибо большое за статьи, благодаря вам, я наконец начал понимать ООП!

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

      Спасибо, что читаете 🙂

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

      Фейнман говорил о квантовой физике… думаю, применимо и к ООП:
      — Если Вам кажется, что Вы поняли квантовую физику, значит Вы ничего не понимаете в квантовой физике
      🙂

      1. Алексей:

        Ануар, ничего, это ничего.
        Главное не сдавайтесь и все получиться.
        Я тоже было месяц-полтора назад запутался с void функцией, задание с падающим мячиком.

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

        Все мы с чего-то начинали.

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

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