- •1. Абстракция и декомпозиция. Основные виды декомпозиции программ.
- •Модульная декомпозиция
- •В заголовочный файл не следует помещать элементы реализации модуля, в том числе и внутренние функции, которые необходимы для реализации, однако не существенны для клиентского когда модуля.
- •Объектная декомпозиция
- •2. Понятие класса и объекта. Переменные-члены и функции-члены. Обращение к членам класса через объект. Указатель this. Константные функции-члены.
- •3. Спецификаторы доступа. Понятие инкапсуляции. Отличие конструкций class и struct. Методы доступа.
- •4. Конструкторы классов, синтаксис, разновидности, моменты вызова конструкторов. Роль конструкторов в соблюдении инвариантов классов.
- •5. Конструкторы по умолчанию (default constructors). Тривиальные и нетривиальные сгенерированные конструкторы классов. Конструирование массивов объектов.
- •6. Списки инициализации. Синтаксис, отличие от присвоений в теле конструктора, необходимость в существовании.
- •7. Деструкторы классов, синтаксис, цель, моменты вызова деструкторов.
- •8. Моменты копирования объектов. Поведение по умолчанию. Конструктор копий и оператор копирующего присвоения.
- •9. Временные объекты. Явные и неявные конструкторы. Оптимизации rvo/nrvo. Временные объекты
- •Неявные и явные конструкторы
- •Запрещение копирования
- •Оптимизация копирования
- •10. Основные отличия между классами-значениями и классами-сущностями. Запрещение копирования объектов. Основные отличия между классами-значениями и классами-сущностями.
- •11. Перемещение объектов. Конструктор перемещения и оператор перемещающего присвоения. Понятие rvalue-ссылки. Функция std::move.
- •12. Перегрузка операторов. Оправданное и неоправданное использование. Пример перегрузки простейшего оператора. Операторы, которые нельзя перегружать.
- •13. Внутриклассовые и глобальные перегруженные операторы. Перегрузка операторов сдвига. Применение перегрузки сдвига для взаимодействия с потоками ввода/вывода.
- •14. Перегрузка операторов сравнения и арифметических операторов. Основные правила реализации и применения.
- •15. Перегрузка операторов индексной выборки, префиксного и постфиксного инкремента/декремента. Перегрузка операторов преобразования типа.
- •16. Статические переменные-члены. Цель применения. Синтаксис. Особенности компоновки.
- •17. Статические функции-члены. Синтаксис, особенности применения. Фабричный метод. Статические функции-члены
- •Фабричный метод
- •19. Физическое и логическое постоянство объектов. Модификатор mutable.
- •20. Класс std::string из стандартной библиотеки. Основная функциональность, способы применения. Особенности внутренней структуры.
- •21. Композиция объектов. Иерархии целое-часть. Структура простейшей композиции по значению в памяти. Ответственность за уничтожение объектов при композиции.
- •22. Ссылочная композиция. Разрываемая композиция. Кратность композиции. Одиночная, множественная и недетерминированная кратность.
- •23. Применение контейнера std::vector для композиции с недетерминированной кратностью. Композиция объектов-значений и объектов-сущностей.
- •24. Композиция объектов с кратностью многие-ко-многим. Основные особенности объектных отношений, способы реализации.
- •25. Наследование классов. Необходимость в отношении наследования. Структура наследования в памяти. Повышающее преобразование типа.
- •26. Критерии оценки корректности применения наследования. Примеры корректного и некорректного применения наследования.
- •27. Конструкторы и деструкторы при наследовании. Моменты и порядок вызовов конструкторов. Передача аргументов конструкторам базового класса.
- •28. Спецификатор доступа protected. Защищенные конструкторы и методы.
- •29. Понижающее преобразование типа (downcast). Опасности. Поля идентификации типов.
- •30. Виртуальные функции. Полиморфизм. Цель. Синтаксис, примеры использования.
- •31. Реализация виртуальных функций. Указатель vptr и таблица vtable. Вызов виртуальной функции. Инициализация служебных данных для работы виртуальных функций в конструкторах.
- •32. Контроль переопределения виртуальных функций. Требования к сигнатурам. Ключевые слова override и final. Ковариантность возвращаемых типов.
- •33. Чисто виртуальные функции и абстрактные классы. Вызов чисто виртуальной функции в конструкторе до завершения инициализации объекта.
- •34. Понятие интерфейса. Применение интерфейсов.
- •35. Множественное наследование конкретных классов. Синтаксис, структура в памяти, особенности применения и реализации.
- •36. Преобразование типов при множественном наследовании в верхнем и нижнем направлениях. Коррекция указателя this.
- •37. Множественное наследование классов с повторяющимся базовым. Синтаксис, структура в памяти, особенности применения и реализации.
- •38. Виртуальные базовые классы. Синтаксис, структура в памяти, особенности применения и реализации. Понятие “самого производного” класса и его роль в организации работы виртуальных базовых классов.
- •39. Механизм rtti - назначение, особенности применения. Структура std::type_info, оператор typeid для выражений и типов.
- •40. Применение оператора dynamic_cast для указателей и ссылок. Основные цели использования. Отличия от операторов static_cast, reinterpret_cast и const_cast.
- •41. Альтернативные решения, заменяющие dynamic_cast. Виртуальные функции для понижающего преобразования. Типовое решение Visitor.
- •42. Обработка исключений. Цели, синтаксис выброса и обработчиков. Выбор обработчика по типу. Передача данных исключения по значению, указателю и ссылке. Исключения языка и стандартной библиотеки.
- •44. Шаблоны функций и классов. Синтаксис определения шаблонов. Инстанцирование шаблонов. Модель включения и явное инстанцирование.
- •Шаблоны классов
- •45. Аргументы шаблонов - типы, константы, шаблонные аргументы шаблонов. Дедукция фактических аргументов шаблонов.
- •46. Понятие обобщенной концепции. Статический полиморфизм по сравнению с динамическим полиморфизмом.
- •Статический полиморфизм
- •47. Итераторы stl - основные разновидности, итераторы контейнеров, итераторы, не связанные с контейнерами.
- •48. Классификация алгоритмов стандартной библиотеки. Примеры применения наиболее часто используемых алгоритмов.
- •49. Функциональные объекты stl. Простые функциональные объекты. Стандартные функциональные объекты. Связыватели std::bind.
- •50. Понятие лямбда-выражения. Синтаксис, особенности использования. Реализация лямбда-выражений компилятором. Список захвата лямбда-выражения.
- •51. Специализация шаблонов. Полная и частичная специализация. Статический выбор вариантов на основе специализации шаблонов.
- •52. Необычный рекуррентный шаблон. Структура, варианты применения.
12. Перегрузка операторов. Оправданное и неоправданное использование. Пример перегрузки простейшего оператора. Операторы, которые нельзя перегружать.
ПЕРЕГРУЗКА ОПЕРАТОРОВ (operator overloading) применяется как альтернатива функциям для пользовательских типов, которым в предметной области свойственен понятный однозначный синтаксис операций. Например, для сложения и умножения матриц вполне естественна и читабельна операторная форма выражений, хотя за этими элементарными на вид операциями скрываются алгоритмы с двойной и тройной вложенностью циклов соответственно:
D = A + B * C
Арифметические, логические и прочие операторы предоставляются языком С++ лишь для встроенных типов данных. Компилятор не в состоянии определить семантику операторов для пользовательских типов самостоятельно - нельзя просто складывать или умножать объекты любых классов одинаковым универсальным способом. В зависимости от смысла абстракции, заложенного автором класса, алгоритм вычисления оператора может иметь сугубо индивидуальный способ реализации, что делает автоматическую реализацию бессмысленной и невозможной.
Применение перегрузки операторов является необязательным, и для любого класса перегрузка операторов может быть заменена использованием обычных функций-членов. Если для реализуемого класса в предметной области операторный стиль является настолько естественным, как для матриц, перегрузка операторов существенно облегчает написание и чтение программ, использующих данный класс. Программист может предоставить собственную функцию, которая будет неявно вызываться при использовании оператора с объектами класса.
Однако, многие программисты злоупотребляют перегрузкой операторов для неподходящих задач, и в результате получают обратный эффект в виде снижения читабельности кода. Когда семантика конкретного оператора не очевидна из контекста, и глядя на операцию над объектами в коде программы не понятно, какие действия скрываются за оператором, то следует вместо перегруженных операторов предпочесть обычные функции-члены, поскольку их смысл легко донести через название.
Нельзя перегружать операторы:
“.” - доступ к члену структуры/класса;
“.*” - доступ к члену структуры/класса через указатель на член;
“::” - оператор разрешения области видимости;
“?:” - тернарный условный оператор.
Перегрузка вышеуказанных операторов не допускается, поскольку обратное могло бы привести к недопустимо сложной неоднозначности интерпретации синтаксиса.
//пример перегрузки унарного оператора “!”
bool CoffeeMachine::operator ! ()
{
if(GetGrainWeidhtLeft()<4)
return 0;
return 1;
}
13. Внутриклассовые и глобальные перегруженные операторы. Перегрузка операторов сдвига. Применение перегрузки сдвига для взаимодействия с потоками ввода/вывода.
● Внутриклассовый оператор ==:
class Date
{ // … без изменений …
public:
// Объявление внутриклассового оператора сравнения на равенство дат
bool operator == ( const Date & d ) const;
};
// Реализация внутриклассового оператора сравнения на равенство дат
inline bool Date::operator == ( const Date & d ) const
{
// Даты равны, когда все их компоненты равны между собой
return m_Year == d.GetYear() &&
m_Month == d.GetMonth() &&
m_Day == d.GetDay() ;
}
● Глобальный оператор ==:
// Реализация глобального оператора сравнения на равенство дат
inline bool operator == ( const Date & d1, const Date & d2 )
{
return d1.GetYear() == d2.GetYear() &&
d1.GetMonth() == d2.GetMonth() &&
d1.GetDay() == d2.GetDay() ;
}
Выбор между внутриклассовой и глобальной перегрузкой операторов часто является стилистической конвенцией. Общая рекомендация состоит в ограничении количества функций, имеющих непосредственный доступ к private-части класса, соответственно, где это возможно и рационально, операторы должны быть глобальными функциями и работать с объектами классов через открытый общедоступный интерфейс. Однако, если операторов немного, и получение прямого доступа к закрытой части класса позволяет реализовать семантику оператора эффективно, внутриклассовая перегрузка всячески приветствуется.
Перегрузка операторов сдвига <<, >>
Как и любые другие операторы, операторы сдвига можно перегрузить в их оригинальном числовом смысле, предусмотренном языком C, если такой семантикой обладает разрабатываемый класс. Однако гораздо чаще такой оператор перегружают для обеспечения взаимодействия объектов классов и потоков ввода-вывода стандартной библиотеки. Система потоков реализована таким удачным образом, что определив операторы сдвига однажды, можно организовать единообразное взаимодействие объектов классов с потоками любого вида - консолью, файлами, буферами памяти.
Когда появляться необходимость вывода объектов на консоль, имеет смысл перегрузить операторы сдвига для соответствующих классов, и получить согласованный синтаксис вывода.
Перегрузить оператор для вывода объектов пользовательских типов относительно легко. Такой оператор может быть только глобальным, внутриклассовый вариант исключается, поскольку первым операндом является сам объект-поток, а определение его класса уже нельзя никак расширить для пользовательского типа. Глобальные перегружаемые операторы таких проблем не имеют:
std::ostream& operator << ( std::ostream& o, const Date & d )
{
o << d.GetYear() << ‘/’ << d.GetMonth() << ‘/’ << d.GetDay();
return o;
}
Возврат ссылки на поток необходим для дальнейшего каскадирования, т.е. для следующих вызовов <<, идущих в инструкции, использующей вывод для объектов классов.
Если требуется решить обратную задачу, а именно чтение объекта-даты из входного потока, нужно добавить подобные определения, однако передавать объект по ссылке с правом на запись:
std::istream & operator >> ( std::istream & i, Date& d )
{ // Считываем строку до пробела
char buf[ 100 ];
i >> buf;
d = Date( buf ); // используем имеющийся конструктор из строки, а затем копируем
return i;
}
int main ()
{ Date d;
std::cin >> d; // ...
}