Вложенные и внутренние классы
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 38 из 104.
Вложенный класс — это класс, определённый внутри другого класса. Этот приём помогает организовать код, группируя связанные типы вместе, и может улучшить инкапсуляцию, скрывая детали реализации.
class LinkedList {
public:
class Node { // Вложенный класс
int data;
Node* next;
public:
Node(int d) : data(d), next(nullptr) {}
friend class LinkedList;
};
private:
Node* head;
public:
LinkedList() : head(nullptr) {}
};Вложенный класс Node тесно связан с LinkedList — он имеет смысл только в этом контексте. Вкладывая его, мы обозначаем эту связь и не засоряем глобальное пространство имён таким общим именем, как "Node".
Вложенные классы подчиняются тем же правилам доступа, которые вы изучили. Вложенный класс, объявленный в закрытой секции, доступен только внутри внешнего класса.
При объявлении как public внешний код может использовать его с оператором области видимости:
LinkedList::Node node(42); // Только если Node является publicОдна важная деталь, касающаяся направления: вложенный class является членом охватывающего class и имеет те же права доступа, что и любой другой член, поэтому он может обращаться к закрытым членам охватывающего class без объявления friend. Однако обратное не происходит автоматически: охватывающий class не имеет доступа к закрытым членам вложенного class.
Вот почему Node объявляет friend class LinkedList; в приведённом выше примере: объявление friend предоставляет охватывающему его LinkedList доступ к закрытым членам Node — это единственное направление, для которого доступ не предоставляется автоматически.
Задание
ЛегкоДавайте создадим простую систему управления задачами, в которой задачи организованы внутри проекта. Это идеальный сценарий для вложенных классов: Task имеет смысл только в контексте Project, поэтому мы вложим его внутрь, чтобы сохранить организованность кода и не засорять глобальное пространство имён.
Вы создадите два файла, чтобы организовать код:
Project.h: определите классProject, содержащий вложенный классTask.Ваш вложенный класс
Taskдолжен быть объявлен в секции public классаProject, чтобы к нему можно было обращаться извне. Он должен хранить имя задачи (строка) и статус выполнения (bool). Добавьте ему конструктор, который принимает имя и по умолчанию устанавливает для задачи статус incomplete. Включите методыcomplete(), чтобы отметить задачу как выполненную,isComplete(), чтобы проверить её статус, иgetName(), чтобы получить имя.Сам класс
Projectдолжен хранить имя проекта (строка) и коллекцию указателей наTask. Предоставьте конструктор, который принимает имя проекта, методaddTask(const std::string& taskName), который создаёт новую задачу и добавляет её в проект, методcompleteTask(const std::string& taskName), который находит задачу по имени и отмечает её как выполненную, а также методdisplayStatus(), который отображает имя проекта, а затем статус каждой задачи.Поскольку
Taskвложен вProject, классProjectможет напрямую работать с задачами. Не забудьте освободить динамически выделенные задачи в деструкторе.main.cpp: объедините всё воедино, считав из ввода имя проекта и имена двух задач (всего три строки). СоздайтеProjectс указанным именем, добавьте в него обе задачи, затем отобразите исходный статус. Завершите первую задачу и снова отобразите статус, чтобы показать изменение.Формат вывода для
displayStatus()должен быть таким:Project: <project_name> - <task_name>: [incomplete] - <task_name>: [complete]Используйте
[complete]или[incomplete]в зависимости от статуса каждой задачи.
Это задание демонстрирует, как вложенные классы помогают организовать связанные типы: класс Task тесно связан с Project и сам по себе не имел бы смысла. Вложив его внутрь, мы ясно обозначаем эту связь в структуре нашего кода.
Попробуйте сами
#include <iostream>
#include <string>
#include "Project.h"
using namespace std;
int main() {
// Прочитать ввод
string projectName;
string task1Name;
string task2Name;
getline(cin, projectName);
getline(cin, task1Name);
getline(cin, task2Name);
// TODO: Создать Project с заданным именем
// TODO: Добавить обе задачи в проект
// TODO: Отобразить начальный статус
// 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Итоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++