Спецификаторы доступа: подробный разбор
Часть раздела Объектно-ориентированное программирование путешествия по 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).
Задание
ЛегкоДавайте создадим систему сравнения кошельков, которая демонстрирует, как объекты одного и того же класса могут получать доступ к приватным членам друг друга. Это ключевой момент в управлении доступом в 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;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер