Урок №114. Спецификаторы доступа public и private

  Юрий  | 

  |

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

 122979

 ǀ   38 

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

Спецификаторы доступа

Рассмотрим следующую программу:

Здесь мы объявляем структуру DateStruct, а затем напрямую обращаемся к её членам для их инициализации. Это работает, так как все члены структуры являются открытыми по умолчанию. Открытые члены (или «public-члены») — это члены структуры или класса, к которым можно получить доступ извне этой же структуры или класса. В программе, приведенной выше, функция main() находится вне структуры, но она может напрямую обращаться к членам day, month и year, так как они являются открытыми.

С другой стороны, рассмотрим следующий почти идентичный класс:

Вам бы не удалось скомпилировать эту программу, так как все члены класса являются закрытыми по умолчанию. Закрытые члены (или «private-члены») — это члены класса, доступ к которым имеют только другие члены этого же класса. Поскольку функция main() не является членом DateClass, то она и не имеет доступа к закрытым членам объекта date.

Хотя члены класса являются закрытыми по умолчанию, мы можем сделать их открытыми, используя ключевое слово public:

Поскольку теперь члены класса DateClass являются открытыми, то к ним можно получить доступ напрямую из функции main().

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

В языке C++ есть 3 уровня доступа:

   спецификатор public делает члены открытыми;

   спецификатор private делает члены закрытыми;

   спецификатор protected открывает доступ к членам только для дружественных и дочерних классов (детально об этом на соответствующем уроке).

Использование спецификаторов доступа


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

Правило: Устанавливайте спецификатор доступа private переменным-членам класса и спецификатор доступа public методам класса (если у вас нет веских оснований делать иначе).

Рассмотрим пример класса, который использует спецификаторы доступа private и public:



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

12/11/2018

Обратите внимание, хоть мы и не можем получить доступ к переменным-членам объекта date напрямую из main() (так как они являются private по умолчанию), мы можем получить доступ к ним через открытые методы setDate() и print()!

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

Некоторые программисты предпочитают сначала перечислить private-члены, а затем уже public-члены. Они руководствуются следующей логикой: public-члены обычно используют private-члены (те же переменные-члены в методах класса), поэтому имеет смысл сначала определять private-члены, а затем уже public-члены. Другие же программисты считают, что сначала нужно указывать public-члены. Здесь уже иная логика: поскольку private-члены закрыты и получить к ним доступ напрямую нельзя, то и выносить их на первое место тоже не нужно. Работать будет и так, и так. Какой способ использовать — выбирайте сами, что вам удобнее.

Рассмотрим следующую программу:

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

В примере, приведенном выше, метод copyFrom() является членом класса DateClass, что открывает ему доступ к private-членам класса DateClass. Это означает, что copyFrom() может не только напрямую обращаться к закрытым членам неявного объекта с которым работает (копия объекта), но и имеет прямой доступ к закрытым членам объекта b класса DateClass!

Это полезно, когда нужно скопировать элементы из одного объекта класса в другой объект того же класса. Детально об этом мы поговорим на следующих уроках.

Структуры vs. Классы

Теперь, когда мы узнали о спецификаторах доступа, мы можем поговорить о фактических различиях между классом и структурой в языке C++. Класс по умолчанию устанавливает всем своим членам спецификатор доступа private. Структура же по умолчанию устанавливает всем своим членам спецификатор доступа public.

Есть еще одно незначительное отличие: структуры наследуют от других конструкций языка С++ открыто, в то время как классы наследуют закрыто.

Тест


Задание №1

a) Что такое открытый член?

Ответ №1.а)

Открытый член — это член класса, доступ к которому имеют объекты как внутри, так и извне класса.

b) Что такое закрытый член?

Ответ №1.b)

Закрытый член — это член класса, доступ к которому имеют только другие члены этого же класса.

c) Что такое спецификатор доступа?

Ответ №1.c)

Спецификатор доступа определяет, кто имеет доступ к членам этого же спецификатора.

d) Сколько есть спецификаторов доступа в языке C++? Назовите их.

Ответ №1.d)

В языке С++ есть 3 спецификатора доступа:

   public;

   private;

   protected.

Задание №2

a) Напишите простой класс с именем Numbers. Этот класс должен иметь:

   три закрытые переменные-члены типа double: m_a, m_b и m_c;

   открытый метод с именем setValues(), который позволит устанавливать значения для m_a, m_b и m_c;

   открытый метод с именем print(), который будет выводить объект класса Numbers в следующем формате: <m_a, m_b, m_c>.

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

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

<3, 4, 5>

Ответ №2.а)

b) Добавьте функцию isEqual() в класс Numbers, чтобы следующий код работал корректно:

Ответ №2.b)

Задание №3

Теперь попробуем что-то посложнее. Напишите класс, который реализует функционал стека. Класс Stack должен иметь:

   закрытый целочисленный фиксированный массив длиной 10 элементов;

   закрытое целочисленное значение для отслеживания длины стека;

   открытый метод с именем reset(), который будет инициализировать значением 0 длину и все значения элементов;

   открытый метод с именем push(), который будет добавлять значение в стек. Метод push() должен возвращать значение false, если массив уже заполнен, в противном случае — true;

   открытый метод с именем pop() для возврата значений из стека. Если в стеке нет значений, то должен выводиться стейтмент assert;

   открытый метод с именем print(), который будет выводить все значения стека.

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

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

( )
( 3 7 5 )
( 3 7 )
( )

Ответ №3

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

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

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

  1. Дмитрий:

    Если переменные члены имеют спецификатор по умолчанию private, а методы public, то почему в
    Visual Studio выдает ошибку на такой код :

    стоит раскомментировать строку //public: и все в порядке.

    1. Павел:

      Члены класса по умолчанию являются private.

      Устанавливать методы public — это рекомендация (правило), потому что взаимодействие с классом происходит через методы.

  2. Дмитрий:

    Задание 3 . Зачем функция push() должна возвращать логические значения , если с этими значения ничего не делают?
    Зачем функция pop() возвращает значение элемента массива, если с этим значением ничего не делают?
    Зачем прописывать спецификаторы privat для членов переменных и public для методов , если члены переменные закрыты , а методы открыты по умолчанию?

  3. Chaos:

    А насколько правильно объявлять члены класса (не методы, именно члены!) protected, а не private в реальных задачах? В реальных, имеется ввиду, в реальных проектах, а не учебных.

  4. Юрий:

  5. Тимур:

    Но ведь этот метод в таком виде не обнуляет значение m_array[m_next], а только возвращает его

    1. Владимир:

      И это правильно! Так работают все массивы и контейнеры в STL.
      Затирание данных, это лишняя трата ресурсов процессора, потому как указатель на данную область вернется ТОЛЬКО в том случае когда будут добавляться данные. А добавления данных в любом случае ПЕРЕЗАПИСЫВАЕТ содержимое ячейки. Так что любое удаление данных в контейнерах это просто смещение указателя, никто не затирает данные.

      1. Павел:

        Хорошо, а зачем тогда резетить массив, когда можно обнулить только указатель?

  6. Константин:

    Написал функции push() и pop() по принципу LIFO. Новые значения заталкиваются в стек через нулевой элемент массива и через него же выталкиваются обратно.

  7. Дмитрий:

    Два вопроса по третьему заданию:

    1. Почему в методе reset нельзя использовать цикл foreach? (у меня выдает такую ошибку — Вызвано исключение: нарушение доступа для записи. this было 0x34F43096.)

    2. Зачем методу push быть логическим? Чем плох такой вариант:

    1. Nikita:

      Присоединяюсь к вашему первому вопросу.
      P.S. Может ему не нравятся изначальные мусорные значения?

    2. Nikita:

      Верно Дмитрий, это и есть ответ на ваш первый вопрос. В целях эксперимента, изначально очистил циклом for, массив до нулевых значений. Только после этих манипуляций, цикл foreach заработал:

      Ну а второй вопрос, то думаю это просто для разнообразия такой способ реализации. Видите, тут "push сделай чтобы вернул 1 или 0, а pop сделай с assertom". Думаю это чисто в целях нашего развития

      1. Дмитрий:

        Благодарю, за обратную связь!

        1. Nikita:

          Вам спасибо за интересный вопрос) Вы бы не спросили, я бы и сам ответа не знал)) Всего вам доброго:)

      2. DarkMatter:

        Вы неправильно понимаете сути работы циклов foreach.

        Дело в том, что в вашем случае, на каждой итерации цикла foreach переменной i присваивается не индекс элемента массива, а значение этого элемента. (Таким образом переменная i и есть элемент, и его тип должен совпадать с типом массива)

        Таким образом декларация: m_stack[i] превращается в m_stack[m_stack[<Номер_итерации>]], а это явно не то, что вам было нужно.

        На самом деле foreach здесь очень даже справляется с задачей:

    3. Егор:

      По вопросу 2.
      Логический тип возврата нужен наверное, чтобы можно было сделать проверку, отработало ли как надо.

      Что-то типа:

  8. Алексей:

    Правда задание 2.а писал без private.

    И так и так работает. Хотя если бы сначала функции public — тогда понятно, без private доступ отовсюду.

  9. somebox:

    Задание 2b.

    Обязательно ли указывать переменную d как ссылку?

    Задание 3.

    Сказано, что: "Класс Stack должен иметь:
    Открытый целочисленный фиксированный массив длиной 10.
    Открытое целочисленное значение для отслеживания длины стека."

    При этом в ответе написано:

    Здесь нет ошибки, или я чего-то не понял?

    1. Алексей:

      Задание 2b.

      Обязательно ли указывать переменную d как ссылку?

      Необходимо. Лучше еще раз почитайте Урок №104. Указатели на функции.

      Я сам протупил с этим, тут всего лишь делается указатель на функцию, которая прочитывает наш point 2 & point 3, делает сравнение.

      Не мой день сегодня, с 3м тоже что-то натупил. 1е сделал быстро и четко.

      1. Кавоз:

        Разве указатель не на объект d?

  10. Алексей:

    Я считаю, что в задании 3, строка 15 вместо:

    нужно использовать, вот это:

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

    1. Nikita:

      Пробовал почти так ,как вы говорите,

      Но это не работает почему то. Значения массива так и остаются мусором а не нулями.

      Просто вы просите обьявить массив внутри функции.. Мы ведь его уже обьявляли выше…

  11. Анатолий:

    Если в классе сначала идут все private члены, а потом public, обязательно ли явно указывать спецификатор privatе? В классе ведь всем членам по умолчанию устанавливается спецификатор доступа private.

    1. Ануар:

      Конечно не обязательно указывать явно, это и подразумевается под "по умолчанию"

      1. Анатолий:

        В некоторых дальнейших уроках фигурирует код с принудительным указанием private, например итоговый тест этой главы второе задание. Это имеет какую то причину, или так совпали звёзды? ))

        1. Ануар:

          так совпали звезды

  12. Ануар:

    Я тут недавно пытался написать программу в одном компиляторе и он заставлял меня в классах везде прописывать const int blabla вместо int blabla , почему ?

  13. Игорь:

    Мой вариант 3 задания

  14. Михаил:

    В задании 2 переменные имеют тип double и сравниваются между собой. Корректно ли это? Например такие не сравниваются (наобум набрал)

  15. Anastasia:

    Что-то я забыла про значение для отслеживания длины стека и написала код без него.
    Корректно получилось?

    1. Никита:

      Момент в том, что у вас такая реализация, что в стек невозможно «положить» число 0. Как раз таки из-за того что вы не использовали доп.переменую обозначающую длину.

      Если я захочу заполнить последний элемент массива нулем, я не могу этого сделать. Вернее могу, но «код» будет думать что там пустое место, а не заложенное пользователем значение 0.
      Короче у вас 0 используется как служебный символ. Из-за этого пользователь не может оперировать этим числом в своих целях , только в целях служебных

  16. Sergey:

    И ещё возник вопрос, правильно ли у меня работает assert hello-site.ru/share/Stek — код программы. priscree.ru/img/ddd58dea7702d7.jpg — скриншот проблемы. Приходится закрывать всплывающее окно, не похоже на нормальную работу программы…

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

      У вас код идентичен с кодом ответа (только другие имена переменных). Всё правильно. Появление ошибки — это нормальное срабатывание стейтмента assert. Assert у вас срабатывает, так как вы вызываете метод pop, прежде чем заполняете стек данными:

      Это бессмысленно. Вы пытаетесь вытянуть элемент из пустого стека. Соответственно, срабатывает assert, который и прописан для такого случая.

  17. Sergey:

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

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

      Так ведь используется стейтмент assert, который выведет сообщение об ошибке, если кто-то попытается вызвать метод pop сразу. Зачем инициализировать нулём?

      1. Евгений:

        Добрый день! МЕня вот какой момент заинтересовал:

        Почему С++ вначале присваивает значение value для нулевого элемента, а потом уже только увеличивает значение m_next++ на единицу? Пытаюсь связать с 38 уроком, но не получается понять порядок действий с++ с этим аргументом.

        1. Никита:

          Мне кажется тут просто нужно вникнуть в разницу между i++ и ++i

    2. Nikita:

      Для этих целей в программе предусмотрена функция reset(), которая присваивает длине значение = 0. Ваше утверждение верно, если мы не будем использовать этот метод

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

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