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

  Юрий  | 

  Обновл. 12 Июн 2019  | 

 23457

 ǀ   12 

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

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

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

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

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

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

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

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

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

Использование списка инициализации почти идентично выполнению прямой инициализации (или 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 (#include 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_blue, m_green и m_alpha. Напишите функцию print(), которая будет выводить значения переменных-членов.

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

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

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

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

Ответ

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

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

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

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

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

    Отличный курс, жаль только не так много времени удиляю, не выходит)

  2. Аватар Андрей:

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

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

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

  3. Аватар somebox:

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

    1. Аватар Dmitrii:

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

      1. Аватар Евгений:

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

  4. Аватар Александр:

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

    1. Юрий Юрий:

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

  5. Аватар Ануар:

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

    1. Юрий Юрий:

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

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

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

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

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

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

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

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

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

telegram канал
НОВОСТИ RAVESLI