Menu
Coddy logo textTech

Спецификаторы доступа: подробный разбор

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 35 из 104.

Теперь, когда вы понимаете три спецификатора доступа, давайте изучим, как они ведут себя, когда у вас есть несколько объектов одного и того же класса. Распространенное заблуждение заключается в том, что private означает «только этот объект», но на самом деле это означает «только этот класс».

Объекты одного и того же класса могут обращаться к приватным членам друг друга:

class Person {
    std::string secret;
    
public:
    Person(std::string s) : secret(s) {}
    
    void readSecret(const Person& other) {
        // Доступ к приватному члену другого объекта — разрешен!
        std::cout << other.secret;
    }
};

Это работает, потому что управление доступом обеспечивается на уровне класса, а не на уровне объекта. Метод readSecret является частью класса Person, поэтому он может обращаться к приватным членам любого объекта Person. Такая архитектура позволяет таким операциям, как конструкторы копирования и операторы сравнения, работать с внутренним состоянием других объектов.

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

class Mixed {
public:
    void publicMethod1();
    
private:
    int privateData;
    
public:
    void publicMethod2();    // Снова public
    
private:
    void privateHelper();    // Снова private
};

Хотя это и допустимый код на C++, обычно лучше группировать члены класса по уровню доступа для улучшения читаемости. Большинство руководств по стилю рекомендуют сначала указывать публичные (public) члены (так как они формируют интерфейс класса), затем защищенные (protected) и, наконец, закрытые (private).

challenge icon

Задание

Легко

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

Вы создадите два файла для организации вашего кода:

  • Wallet.h: Определите класс Wallet, который хранит приватные финансовые данные, но позволяет сравнивать кошельки между собой. Ваш класс должен содержать:
    • Приватные члены: ownerName (string) и balance (double)
    • Конструктор, который принимает имя владельца и начальный баланс
    • Публичный геттер getOwnerName(), который возвращает имя владельца
    • Метод hasMoreThan(const Wallet& other), который сравнивает баланс этого кошелька с приватным балансом другого кошелька, возвращая true, если в этом кошельке больше денег
    • Метод combinedBalance(const Wallet& other), который возвращает сумму приватных балансов обоих кошельков
    • Метод transferTo(Wallet& other, double amount), который переводит деньги из этого кошелька в другой — он должен напрямую изменять приватные балансы обоих кошельков (только если сумма положительна и не превышает баланс этого кошелька)
  • main.cpp: Продемонстрируйте доступ к приватным членам между объектами. Считайте два имени владельцев и два баланса из входных данных (четыре строки: name1, balance1, name2, balance2), затем:
    • Создайте два объекта Wallet с входными значениями
    • Выведите "Combined wealth: "Combined wealth: $"lt;amount>", используя combinedBalance()
    • Сравните кошельки и выведите "<name> has more money" для того владельца, у которого баланс выше (используйте hasMoreThan() для определения этого)
    • Переведите 100.0 из первого кошелька во второй, используя transferTo()
    • Выведите "After transfer:"
    • Снова выведите "Combined wealth: "Combined wealth: $"lt;amount>" (значение должно остаться прежним)
    • Снова сравните их и выведите "<name> has more money" (результат мог измениться)

Ключевая концепция здесь заключается в том, что такие методы, как hasMoreThan() и transferTo(), могут напрямую обращаться к приватному члену balance другого объекта Wallet — геттеры не нужны. Это работает, потому что контроль доступа в C++ основан на классах: любой код внутри класса Wallet может обращаться к приватным членам любого объекта Wallet.

Форматируйте все денежные значения с двумя знаками после запятой, используя std::fixed и std::setprecision(2) из библиотеки <iomanip>. Преобразуйте входные данные баланса с помощью std::stod().

Шпаргалка

Управление доступом в C++ осуществляется на уровне класса, а не на уровне объекта. Это означает, что объекты одного и того же класса могут обращаться к закрытым (private) членам друг друга:

class Person {
    std::string secret;
    
public:
    Person(std::string s) : secret(s) {}
    
    void readSecret(const Person& other) {
        // Доступ к закрытому члену другого объекта — разрешен!
        std::cout << other.secret;
    }
};

Метод readSecret может обращаться к закрытому члену secret любого объекта Person, так как он является частью класса Person. Такая архитектура позволяет реализовывать такие операции, как конструкторы копирования и операторы сравнения.

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

class Mixed {
public:
    void publicMethod1();
    
private:
    int privateData;
    
public:
    void publicMethod2();    // Снова public
    
private:
    void privateHelper();    // Снова private
};

Тем не менее, для лучшей читаемости кода рекомендуется группировать члены класса по уровню доступа. Большинство руководств по стилю советуют сначала указывать публичные (public) члены, затем защищенные (protected) и в конце — закрытые (private).

Попробуйте сами

#include <iostream>
#include <string>
#include <iomanip>
#include "Wallet.h"

using namespace std;

int main() {
    // Считать входные данные: name1, balance1, name2, balance2
    string name1, name2;
    string balanceStr1, balanceStr2;
    
    getline(cin, name1);
    getline(cin, balanceStr1);
    getline(cin, name2);
    getline(cin, balanceStr2);
    
    double balance1 = stod(balanceStr1);
    double balance2 = stod(balanceStr2);
    
    // Установить форматирование вывода для денежных значений
    cout << fixed << setprecision(2);
    
    // TODO: Создать два объекта Wallet с входными значениями
    
    // TODO: Вывести общее состояние, используя combinedBalance()
    // Формат: "Combined wealth: $<amount>"
    
    // TODO: Сравнить кошельки с помощью hasMoreThan() и вывести, у кого больше денег
    // Формат: "<name> has more money"
    
    // TODO: Перевести 100.0 из первого кошелька во второй с помощью transferTo()
    
    // TODO: Вывести "After transfer:"
    
    // TODO: Снова вывести общее состояние
    
    // TODO: Снова сравнить и вывести, у кого больше денег
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование