Спецификаторы доступа: подробный разбор
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 35 из 104.
Теперь, когда ты понимаешь три спецификатора access, давай разберёмся, как они ведут себя, когда у тебя есть несколько объектов одного и того же class. Распространённое заблуждение состоит в том, что private означает «только этот объект», но на самом деле это означает «только этот class».
Объекты одного и того же класса могут обращаться к закрытым членам друг друга:
class Person {
std::string secret;
public:
Person(std::string s) : secret(s) {}
void readSecret(const Person& other) {
// Доступ к приватному члену другого объекта — разрешён!
std::cout << other.secret;
}
};Это работает, потому что access control обеспечивается на уровне class, а не на уровне объекта. Метод readSecret является частью Person class, поэтому он может получать доступ к закрытым членам любого объекта Person. Такая конструкция позволяет таким операциям, как конструкторы копирования и операторы сравнения, работать с внутренним состоянием других объектов.
Вы также можете несколько раз переключаться между спецификаторами access внутри class:
class Mixed {
public:
void publicMethod1();
private:
int privateData;
public:
void publicMethod2(); // Снова public
private:
void privateHelper(); // Снова private
};Хотя это допустимый C++, обычно для удобства чтения лучше группировать члены по уровню access. В большинстве руководств по стилю рекомендуется сначала перечислять public-члены (поскольку они образуют интерфейс class), затем protected, а после них — private.
Задание
ЛегкоДавайте создадим систему сравнения кошельков, демонстрирующую, как объекты одного и того же class могут получать доступ к приватным членам друг друга. Это важное понимание принципа управления доступом в C++: он применяется на уровне class, а не на уровне объекта.
Вы создадите два файла, чтобы организовать код:
Wallet.h: определите classWallet, который хранит приватные финансовые данные, но позволяет сравнивать кошельки друг с другом. Ваш class должен иметь:- Приватные члены:
ownerName(строка) иbalance(число double) - Конструктор, принимающий имя владельца и initial balance
- Публичный getter
getOwnerName(), возвращающий имя владельца - Метод
hasMoreThan(const Wallet& other), который сравнивает balance этого кошелька с приватным balance другого кошелька и возвращаетtrue, если в этом кошельке больше денег - Метод
combinedBalance(const Wallet& other), возвращающий сумму приватных balance обоих кошельков - Метод
transferTo(Wallet& other, double amount), который перемещает деньги из этого кошелька в another кошелёк. Он должен напрямую изменять приватные balance обоих кошельков (только если amount положителен и не превышает balance этого кошелька)
- Приватные члены:
main.cpp: продемонстрируйте доступ к приватным членам между объектами. Считайте из input два имени владельцев и два значения balance (четыре строки: name1, balance1, name2, balance2), затем:- Создайте два объекта
Walletс указанными во входных данных значениями - Выведите
"Combined wealth: $<amount>"с использованиемcombinedBalance() - Сравните кошельки и выведите
"<name> has more money"для владельца с более высоким balance (используйтеhasMoreThan(), чтобы определить это) - Переведите
100.0из первого кошелька во второй с помощьюtransferTo() - Выведите
"After transfer:" - Снова выведите
"Combined wealth: $<amount>"(значение должно остаться неизменным) - Снова сравните кошельки и выведите
"<name> has more money"(результат может измениться)
- Создайте два объекта
Ключевая идея заключается в том, что такие методы, как hasMoreThan() и transferTo(), могут напрямую обращаться к приватному члену balance другого объекта Wallet: геттеры не нужны. Это работает, потому что управление доступом в C++ основано на class: любой код внутри class Wallet может обращаться к приватным членам любого объекта Wallet.
Форматируйте все денежные значения с двумя знаками после запятой, используя std::fixed и std::setprecision(2) из <iomanip>. Преобразуйте входные значения balance с помощью std::stod().
Попробуйте сами
#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: Print combined wealth using combinedBalance()
// Format: "Combined wealth: $<amount>"
// TODO: Compare wallets using hasMoreThan() and print who has more money
// Format: "<name> has more money"
// TODO: Перевести 100.0 из первого кошелька во второй с помощью transferTo()
// TODO: Print "After transfer:"
// TODO: Print combined wealth again
// TODO: Compare again and print who has more money
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Итоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++