Date Редакция Категория comp Теги Cpp

Симпатичная книжка Лаптев В.В. C++. Объектно-ориентированное программирование: Учебное пособие. СПб.: Питер, 2008. 464 с. в конце каждой главы содержит список тестовых вопросов по С++, на которые я решил ответить. Поскольку вопросы относятся к материалу конкретной главы, то ответы на них ограничены пройденным к этому моменту. В ответах могут быть ошибки!

Некоторые вопросы не имеют прямых ответов в тексте (особенно это касается главы 14). Например, вопрос 1 главы 3 о том, почему нельзя перегружать некоторые операции. Немного погуглив, нашёл и ответы, и источник вопросов: Bjarne Stroustrup's C++ Style and Technique FAQ.

Ну и, неизбежные в любой книге опечатки.

Глава 1. Классы и объекты

1. Что определяет класс? Чем отличается класс от объекта?

Класс определяет новый тип. Объект – переменная этого нового типа, иначе говоря, экземпляр класса. Класс объявляется один раз, а объектов этого класса создаётся столько, сколько необходимо.

2. Можно ли объявлять массив объектов? А массив классов?

Да. Нет.

3. Разрешается ли объявлять указатель на объект? А указатель на класс?

Да. Нет.

4. Можно ли совместить определение класса с объявлением объекта?

Да.

5. Объясните разницу между определением класса и объявлением класса.

Объявление говорит компилятору, что в программе существует элемент с указанным именем. Определение указывает какой код или данные соответствуют этому имени. Поэтому определение должно быть уникальным, для объявлений это необязательно. Определение класса позволяет объявлять объекты этого класса, а объявление класса – нет.

6. Объясните, чем различаются два объявления указателя:

TClass *p = new TClass;
TClass *p = new TClass();

В первом случае выделяемая память инициализируется «мусором», во втором – нулями.

7. Как называется использование объекта одного класса в качестве поля другого класса?

Включение или композиция.

8. Является ли структура классом? Чем класс отличается от структуры?

Да. В классе все поля по умолчанию приватны, а в структуре – публичны.

9. Какие ключевые слова в C++ обозначают класс?

class, struct, union.

10. Объясните принцип инкапсуляции.

Сокрытие деталей реализации класса.

11. Для чего нужны ключевые слова public и private? Можно ли использовать ключевые слова public и private в структуре?

Ключевое слово public открывает доступ извне к полям класса, а слово private — закрывает. Да.

12. Существуют ли ограничения на использование ключевых слов publiс и private в классе? А в структуре?

И в классе, и в структуре разрешается написать столько слов public и private, сколько необходимо, и в том порядке, который требуется. Очередное слово действует до следующего.

13. Обязательно ли делать поля класса приватными? Как инициализировать приватные поля класса?

Нет. С помощью публичных методов.

14. Что такое «метод»? Как вызывается метод? Может ли метод быть приватным?

Метод – это функция, определённая в классе. Обычный метод method вызывается для объекта класса (object.method()), статический метод вызывается для класса (class.method()). Да

15. Как определить метод непосредственно внутри класса? А вне класса?

Внутри – также, как обычную функцию. Вне – с помощью префикса – имени класса в заголовке определения метода: class::method().

16. Объясните, что понимается под интерфейсом класса.

Открытая (публичная) часть класса.

17. Что обозначается ключевым словом this? Для чего может использоваться конструкция *this?

Ключевое слово this является указателем на объект, для которого вызван метод. Значение *this можно использовать для работы с текущим объектом.

18. Что такое композиция?

Использование объекта одного класса в качестве поля другого класса.

19. Разрешается ли внутри метода объявлять объекты «своего» класса? Как присваивать таким объектам начальное значение?

Да. С помощью параметров, переданных в метод; с помощью *this.

20. Сколько места в памяти занимает объект класса? Как это узнать?

Объект будет занимать в памяти места не меньше, чем все его поля, кроме статических. Узнать это можно с помощью sizeof(class). Кроме того, к размеру объекта могут будут добавлены: указатели на виртуальные функции, размер нестатических полей базовых классов, а также пробелы, добавленные компилятором для выравнивания данных.

21. Каков размер «пустого» объекта? Влияют ли методы на размер объекта?

«Пустой» объект – это объект «пустого» класса. Размер такого объекта равен 1 char (в 32-битном gcc). Размер принят ненулевым, чтобы убедиться, что два разных объекта будут иметь разные адреса. Методы на размер объекта не влияют.

22. Одинаков ли размер класса и аналогичной структуры?

Да.

23. Что такое выравнивание и от чего оно зависит? Влияет ли выравнивание на размер класса?

Выравнивание – это способ размещения данных в памяти особым образом для ускорения доступа. Оно зависит от реализации компилятора, ОЗУ и процессора, установок режимов компилятора и компоновщика. Выравнивание влияет на размер класса.

24. Покажите, как осуществить выравнивание полей класса по границе двух байтов.

#pragma pack(2)        // или
#pragma pack(push, 2)

25. Разрешается ли параметрам методов присваивать значение по умолчанию?

Да.

26. Объясните, почему методы, реализующие бинарные операции (например, сложение), должны иметь один параметр.

Этот параметр является правым аргументом бинарной операции, левым аргументом является объект, вызвавший метод: object.method(parameter).

27. Объясните назначение директивы #pragma.

Она предназначена для установок режимов компилятора и компоновщика. В частности, для выравнивания по границе байта необходимо задать аргумент pack(l).

28. Какой принцип объектно-ориентированного программирования проявляется в перегрузке методов?

Полиморфизм.

Глава 2. Конструкторы

1. Дайте определение конструктора. Каково назначение конструктора?

Конструктор – это особый метод, предназначенный для инициализации объекта данного класса.

2. Перечислите отличия конструктора от метода.

Конструктор не имеет собственного имени – его имя совпадает с именем класса. Конструктор не возвращает результата (даже void).

3. Сколько конструкторов может быть в классе? Допускается ли перегрузка конструкторов?

Сколько угодно. Да.

4. Какие виды конструкторов создаются по умолчанию?

Конструктор по умолчанию, конструктор копирования.

5. Может ли конструктор быть приватным? Какие последствия влечет за собой объявление конструктора приватным?

Да. Невозможность создавать объекты данного класса.

6. Приведите несколько случаев, когда конструктор вызывается неявно.

Копирование, конструирование внутренних/временных анонимных объектов, передача по значению, приведение типа.

7. Как инициализировать динамическую переменную?

При помощи new и конструктора данного класса. Для класса X: X *x = new X();

8. Как объявить константу в классе? Можно ли объявить дробную константу?

Константу в классе можно объявить как: 1) перечислимый тип enum; 2) объявить и инициализировать статическое поле целочисленного типа: static const int i = 7; 3) константное поле класса: const double x;

Да, можно.

9. Каким образом разрешается инициализировать константные поля в классе?

С помощью списка инициализации конструктора.

10. В каком порядке инициализируются поля в классе? Совпадает ли этот порядок с порядком перечисления инициализаторов в списке инициализации конструктора?

Поля получают значения в порядке объявления в классе. Вообще говоря, нет.

11. Какие конструкции C++ разрешается использовать в списке инициализации в качестве инициализирующих выражений?

Любое вычисляемое выражение, приводимое к типу инициализируемого поля.

12. Влияет ли наличие целочисленных констант-полей на размер класса?

Да, если только это не статические константы.

13. Объясните, что такое «инициализация нулем».

Если имя поля присутствует в списке инициализации, а инициализирующее значение для него отсутствует (field()), то выполняется «инициализация нулем»: для встроенных типов – обнуление значения поля, для невстроенных – вызов конструктора по умолчанию.

14. Что такое деструктор? Может ли деструктор иметь параметры? Допускается ли перегрузка деструкторов?

Специальный метод, автоматически вызываемый при уничтожении объекта своего класса.

Нет.

Нет.

15. Зачем нужны константные методы? Чем отличается определение константного метода от определения обычного?

Для работы с объектами-константами; чтобы предотвратить случайное изменение данных внутри метода и показать, что метод не меняет состояние класса. Ключевым словом const в заголовке метода: method() const

16. Может ли константный метод вызываться для объектов-переменных? А обычный метод — для объектов-констант?

Да. Нет.

Глава 3. Перегрузка операций

1. Какие операции нельзя перегружать? Как вы думаете, почему?

  • селектор компонента объекта (.);
  • разыменование указателя на компонент класса (.*);
  • тернарный условный оператор (? :);
  • указание области видимости (::);
  • определение размера аргумента (sizeof).

Соображения о том, почему нельзя перегружать эти операции приведены здесь.

2. Можно ли перегружать операции для встроенных типов данных?

Нет.

3. Можно ли при перегрузке изменить приоритет операции?

Нет.

4. Можно ли определить новую операцию?

Нет, нельзя определить свою собственную операцию. Хотя можно придать новый смысл одной из встроенных операций. Например, операцию инкремента для нового класса строк можно перегрузить как операцию перевода в верхний регистр и т. п.

5. Можно ли перегрузить операцию «запятая»?

Да.

6. Чем отличается функциональная форма вызова бинарной операции от инфиксной формы?

  • функциональная форма a.operator&(b)
  • инфиксная форма a & b

7. Перечислите особенности перегрузки операций методами класса. Чем отличается перегрузка внешним образом от перегрузки как метод класса?

У операций, перегруженных методами класса один параметр — текущий объект — определен по умолчанию. Поэтому, если операция перегружена методом класса, то количество явно определяемых параметров у неё на один меньше: у бинарных операций параметр один, а у унарных параметр отсутствует. У операций, перегруженных внешними функциями, все параметры заданы явно и их количество совпадает с типом операции.

8. Какой результат должны возвращать операции с присваиванием?

Операции с присваиванием должны возвращать ссылку на объект своего класса.

9. Объясните, что такое l-значение.

Выражение, которое может стоять слева от знака операции присваивания.

10. Как различаются перегруженная префиксная и постфиксная операции инкремента и декремента?

Префиксная форма должна возвращать ссылку, а постфиксная — значение. Постфиксная форма операции должна иметь фиктивный аргумент типа int.

11. Что означает выражение *this? В каких случаях оно используется?

Значение указателя на текущий объект. Используется для работы с текущим объектом.

12. Какие операции не рекомендуется перегружать как методы класса? Почему?

Бинарные операции, левым аргументом которых может быть объект, тип которого отличается от реализуемого типа (сложение, умножение и т. п.).

13. Объясните, почему при перегрузке операций нельзя задавать параметры по умолчанию?

Потому что запрещено изменять синтаксис операций.

14. Какие операции разрешается перегружать только как методы класса?

  • присваивание (=);
  • вызов функции (());
  • индексирование ([]);
  • доступ по указателю (->).

15. Перечислите все возможные способы задания явного преобразования типов.

Унаследованные от C:

  • (тип) выражение
  • тип (выражение)

Введенные в C++:

  • static_cast<тип>(выpaжeниe)
  • rеintеrрrеted_саst<тип>(выражение)
  • dynamic_cast<тиn>(выражение)

16. Дайте определение дружественной функции. Как объявляется дружественная функция? А как определяется?

Дружественная функция — это внешняя функция, имеющая доступ к скрытым полям класса. Объявляется в интерфейсе класса, перед прототипом добавляется слово friend. Определение дружественной функции не должно содержать в заголовке ни слова friend, ни префикса класса.

17. Объясните, какую операцию выполняет конструкция static_cast<>?

Приведение типов с проверкой во время компиляции.

18. Какой вид конструктора фактически является конструктором преобразования типов?

Конструктор инициализации.

19. Чем различаются операции reinterpret_cast<> и static_cast<>?

reinterpret_cast<> выполняет преобразование типов без проверки во время компиляции, а static_cast<> — с проверкой.

20. Для чего нужны функции преобразования? Как объявить такую функцию в классе?

Функция преобразования выполняет преобразование типа класса в другой тип, т. е. выполняет операцию обратную той, что выполняет конструктор инициализации. Функция преобразования обязательно должна быть методом класса. В объявлении не должны задаваться ни тип возвращаемого значения, ни список параметров: operator type ();

21. Объясните назначение операции const_cast<>.

Оператор const_cast<> позволяет временно отменить константность.

22. Как запретить неявное преобразование типа, выполняемое конструктором инициализации?

Объявить этот конструктор как explicit.

23. Какие проблемы могут возникнуть при определении функций преобразования?

Может возникнуть неоднозначность при преобразовании типов.

24. Для чего служит ключевое слово explicit?

Оно запрещает неявный вызов данного конструктора, препятствуя, таким образом, неявному преобразованию типов.

25. Как с помощью функции преобразования и конструктора инициализации определить «новые» операции для встроенных типов данных.

Для этого нужно определить класс-оболочку для встроенного типа и реализовать в этом классе преобразование типов и требуемые операции.

Глава 4. Массивы и классы

1. Можно ли объявить в классе поле-массив? Каким образом задается размер поля-массива?

Да.

Задать количество элементов массива можно либо явной константой, либо константным выражением со статическими и (или) перечислимыми константами, причем константы должны быть определены раньше массива.

2. Каким образом инициализируется поле-массив?

Инициализировать поле-массив можно в теле конструктора. Кроме этого, можно с помощью списка инициализации конструктора присвоить элементам массива нулевые значения.

3. Какой результат должна возвращать перегруженная операция индексирования? Почему?

Операция индексирования должна возвращать ссылку, так как выражение имя[индекс] может стоять как справа, так и слева от знака.

4. Почему операцию индексирования перегружают в двух вариантах?

Операцию индексирования обычно реализуют в константном и неконстантном варианте. Неконстантный метод работает, когда выражение имя[индекс] стоит слева от знака присваивания. Константный метод вызывается, когда имя[индекс] стоит справа от знака присваивания и текущий объект не изменяется.

5. Объясните проблемы, возникающие в случае аварийного завершения работы конструктора.

Конструктор не может вернуть код завершения, сигнализирующий об ошибке. Поэтому прекратить выполнение операции и сообщить об этом в вызывающую программу можно только с помощью механизма обработки исключений.

6. Как определяются статические поля класса?

Статические поля определяются вне класса (для статических констант целочисленного типа допускается инициализация внутри класса). При определении статические поля могут быть проинициализированы. По умолчанию статические поля класса инициализируются нулями.

7. Какие элементы класса можно объявлять статическими?

Поля и методы.

8. Можно ли объявить в классе статическую константу? А константный статический массив?

Да.

Да.

9. Какие статические поля можно инициализировать непосредственно в классе?

Статические константы целочисленного типа.

10. Как определяются статические поля? В какой момент работы программы выполняется инициализация статических полей?

Вне класса. типа). При этом слово static в операторе инициализации отсутствует. Статические поля создаются в единственном экземпляре при запуске программы, до создания любых объектов класса.

11. Сколько места в классе занимают статические поля?

Статические поля не занимают место в классе.

12. Чем отличается статический метод от обычного?

Статический метод можно вызывать для класса независимо от того, определён ли хотя бы один объект этого класса: class::staticmethod(). Так как статический метод не «приписан» к объекту, он не получает указателя this в качестве параметра. Статические методы не могут быть константными или виртуальными.

13. Какие методы класса не могут быть статическими?

Конструкторы, деструктор, константные методы, виртуальные методы.

14. Можно ли статический метод вызвать для объекта?

Да.

15. Какие варианты применения статических полей вы можете привести? А каким образом применяются статические методы?

Поля: когда данные (например, массив) нужны в единственном экземпляре, но без нарушения инкапсуляции. Методы: счётчик количества объектов класса; для запуска нужного конструктора.

Глава 5. Динамическая память в C++

1. Что является единицей памяти в C++? Какие требования к размеру единицы памяти прописаны в стандарте C++?

Байт.

Байт должен быть такого размера, чтобы в нём был способен поместиться любой символ из базового набора символов.

2. В каких единицах выдает результат операция sizeof? Какие типы данных имеют размер 1?

  • sizeof выдаёт размер объекта или типа в байтах.
  • char, signed char, unsigned char.

3. Какие три вида памяти входят в модель памяти C++?

  • статическая;
  • автоматическая;
  • динамическая.

4. Какие формы операций new и delete вы знаете? В чем их различие?

Формы new различаются поведением в случае невозможности выделения памяти. Одна из них генерирует исключение bad_alloc, другая возвращает нулевой указатель и не генерирует исключений (требуется подключение #include <new>). Каждая new имеет дополняющую её delete.

5. Какие типы являются POD-типами? В чем различие работы механизма выделения и освобождения памяти с помощью операций new и delete при работе с POD- и nonPOD-объектами?

POD-типами (Plain Old Data) являются все типы, изначально определенные в С (встроенные типы, перечисления, указатели, массивы, структуры и объединения), а также классы, все поля которых являются POD-объектами и которые не являются наследниками, не имеют конструкторов и виртуальных функций.

Операции создания/уничтожения объектов POD-типов выполняются за один шаг: при создании выделяется требуемая под объект память, при уничтожении эта память освобождается. Для nonPOD-типов каждая из операций создания/уничтожения динамических объектов выполняется за два шага: при создании сначала выделяется память, а потом вызываются конструкторы (без аргументов или инициализации); при уничтожении — сначала вызывается деструктор, а потом возвращается память.

6. С помощью каких операций в C++ выделяют память?

Динамическая память выделяется объектам во время выполнения программы с помощью операций new и new[]. Для выделения статической и автоматической памяти специальных операций не предусмотрено.

7. Чем отличается операция new от операции new[]?

С помощью new можно выделить память и инициализировать одиночные объекты, а с помощью new[] память выделяется под массивы, инициализировать которые при создании невозможно.

8. Почему для динамических классов-контейнеров деструктор надо писать явным образом?

Деструктор должен возвращать динамическую память, запрошенную в конструкторе.

9. С помощью каких операций в C++ осуществляется возврат динамической памяти?

Динамическая память возвращается с помощью операций delete и delete[].

10. Что такое «глубокое копирование» и когда в нём возникает необходимость? Какое копирование осуществляет стандартный конструктор копирования?

Необходимость возникает, например, при копировании массивов. При глубоком копировании target = source создаётся динамический массив размера source, а затем в новый массив копируются элементы source.

Стандартный конструктор копирования выполняет поверхностное копирование, приравнивая указатели target и source. В результате получаем две ссылки, указывавшие на один и тот же массив и все изменения target автоматически появятся в source и наоборот. В частности, при уничтожении target будет уничтожено и содержимое source, а ссылка на source «провиснет».

11. Чем отличается копирование от присваивания?

При присваивании необходимо возвратить системе память прежнего динамического объекта, при копировании в этом нет необходимости.

12. Объясните, почему в реализации операции присваивания требуется проверка присваивания самому себе.

Проверка самоприсваивания позволяет избежать лишней работы: не требуется создавать новый динамический массив и копировать в него элементы.

13. Сколько аргументов у операции присваивания?

Два.

14. Можно ли в качестве операции индексирования использовать операцию вызова функции ()? В чем её преимущества перед операцией []?

Да.

С помощью () удобнее индексировать многомерные массивы (так как количество аргументов функции может быть произвольным).

15. Почему необходимо писать два определения операции индексирования? Чем они различаются?

Константный метод используется для работы с константными параметрами, когда имя[индекс] стоит справа от знака присваивания и текущий объект не изменяется. Неконстантный метод работает, когда выражение имя[индекс] стоит слева от знака присваивания. Один вариант константный, второй – нет.

Глава 6. Контейнеры

1. Дайте определение контейнера.

Контейнер – это тип данных, позволяющий хранить и извлекать объекты независимо от их содержимого. Экземпляр контейнера представляет собой набор объектов других типов. Например, контейнер vector<int> хранит целые числа.

2. Какие виды встроенных контейнеров в C++ вы знаете?

Массивы, vector, list, deque, map, ...

3. Какие способы доступа к элементам контейнера вам известны?

Прямой (по индексу), последовательный, ассоциативный.

4. Чем отличается прямой доступ от ассоциативного?

В прямом доступе ключом является индекс элемента – целое число без знака.

5. Перечислите операции, которые обычно реализуются при последовательном доступе к элементам контейнера.

  • переход к следующему элементу;
  • переход к предыдущему элементу;
  • переход к первому элементу;
  • переход к последнему элементу;
  • переход на n элементов вперед;
  • переход на n элементов назад;
  • получение значения текущего элемента;
  • изменение значения текущего элемента.

6. Дайте определение итератора.

Итератор — это объект, обеспечивающий последовательный доступ к элементам контейнера.

7. Можно ли реализовать последовательный доступ без итератора? В чем преимущества реализации последовательного доступа с помощью итератора?

Да. Например, с помощью методов класса-контейнера.

Использование итераторов позволяет отделить интерфейс доступа к элементам контейнера от интерфейса контейнера.

8. Что играет роль итератора для массивов C++?

Указатель.

9. Дайте определение вложенного класса.

Вложенным называется класс, объявленный внутри другого класса.

10. Можно ли класс-итератор реализовать как внешний класс? А как вложенный? В чем различия этих способов реализации?

Да.

Да.

Во втором случае объект "знает" о типе итератора заранее.

11. Может ли объемлющий класс иметь неограниченный доступ к элементам вложенного класса? А вложенный класс — к элементам объемлющего?

Объемлющий класс по умолчанию не имеет доступа к приватным элементам вложенного класса. Чтобы это было возможно, нужно использовать механизм дружественных классов.

Вложенный класс имеет неограниченный доступ к элементам объемлющего класса, так как сам вложенный класс является элементом объемлющего класса.

class Outer

{
public:

   Outer()
      : m_resource(new int(-1))
   {}

public:

   class Inner
   {
   public:

      void Free(Outer & outer)
      {
         delete outer.m_resource;
      }
   };


private:

   int * m_resource;

};

12. Ограничена ли глубина вложенности классов?

Нет.

13. Можно ли определить вложенный класс внешним образом? Зачем это может понадобиться?

Да.

Это позволяет написать его определение в другом файле и транслировать его отдельно от объемлющего класса.

14. Каким образом вложенный класс может использовать методы объемлющего класса? А объемлющий — методы вложенного?

Как и для обычных невложенных классов необходимо объявить объект (или указатель), для которого вызвать нужный метод.

15. Что такое «запредельный» элемент, какую роль он играет в контейнерах?

Элемент, находящийся за последним элементом контейнера. Он играет роль предела при проверке завершения перебора элементов контейнера — цикл продолжается до тех пор, пока итератор не равен запредельному элементу.

16. Дайте определение стека, очереди и дека.

Стек, очередь и дек — разновидности последовательных контейнеров, отличающиеся способом вставки и удаления элементов.

Стек — контейнер, в котором вставка и удаление элементов выполняются только на одном конце контейнера.

Очередь — контейнер, в котором вставка выполняется на одном, а удаление — на другом конце контейнера.

Контейнер, в котором вставка и удаление выполняется на обоих концах контейнера называется деком.

17. Объясните назначение методов begin() и end().

Метод begin() возвращает итератор, установленный в начало последовательности элементов контейнера. Метод end() возвращает итератор, установленный на позицию за последним элементом последовательности (запредельный элемент).

18. Обязательно ли при реализации контейнеров программировать операцию присваивания? А конструктор копирования?

Да.

Да.

Чтобы избежать поверхностного копирования, когда несколько контейнеров делят между собой один и тот же ресурс.

19. Приведите минимальный интерфейс, который необходимо реализовать при разработке класса-стека.

  • добавление элемента в стек — push();
  • удаление элемента из стека — рор();
  • проверка, не пустой ли стек — empty().

20. Объясните, по каким причинам трудно написать универсальный контейнер, элементы которого могут иметь произвольный тип.

Трудно написать универсальный класс без использования механизма наследования или шаблонов. Для этого пришлось бы задействовать нетипизированные указатели, которые потенциально опасны.

Глава 7. Исключения

1. Объясните, зачем нужны исключения?

В первую очередь, в связи с необходимостью как-то реагировать на некорректное поведение программы во время работы конструкторов и перегруженных операций (вернуть информацию об ошибках в которых другим способом затруднительно).

2. Назовите ключевые слова C++, которые используются при обработке исключений.

throw, try, catch.

3. Исключение — это:

  1. событие;
  2. ситуация;
  3. объект;
  4. ошибка в программе;
  5. прерывание.

Объект.

4. Каким образом исключение генерируется?

С помощью оператора throw, а также различных механизмов C++ (при выделении памяти, динамической идентификации типов, вводе-выводе, при нарушении функцией спецификации исключения).

5. Каковы функции контролируемого блока?

Проверка возникновения исключения.

6. Что обозначается ключевым словом catch?

  1. контролируемый блок;
  2. блок обработки исключения;
  3. секция-ловушка;
  4. генератор исключения;
  5. обработчик прерывания.

Блок обработки исключения = секция-ловушка.

7. Какого типа может быть исключение?

Одного из встроенных (exception и его наследников), либо определённого программистом.

8. Сколько параметров разрешается писать в заголовке секции-ловушки?

Не более одного.

9. Какими способами разрешается передавать исключение в блок обработки?

Любым способом: по значению, по ссылке, по указателю.

10. Объясните, каким образом преодолеть ограничение на передачу единственного параметра в блок обработки.

Поместить нужное количество блоков обработки, использовать блок обработки с указанием только типа исключения, создать структуру с кортежем параметров.

11. Почему нельзя выполнять преобразования типов исключений при передаче в секцию-ловушку?

Если бы преобразования были разрешены, то невозможно было бы перехватывать исключение конкретного типа.

12. В каких случаях преобразование типа все же выполняется?

Когда в качестве формального параметра в секции-ловушке используется базовый класс, внутрь секции может попасть объект производного класса, являющегося разновидностью базового (принцип подстановки).

13. Напишите конструкцию, которая позволяет перехватить любое исключение.

catch (...) {}

14. Могут ли контролируемые блоки быть вложенными?

Да.

15. Объясните термин «раскрутка стека».

Процесс уничтожения локальных объектов при выходе по исключению.

16. Зачем нужен «контролируемый блок-функция» и чем он отличается от обычного контролируемого блока?

Для перехвата исключений в списке инициализации конструктора. Контролируемый блок-функция является телом функции, а не находится внутри другой функции как обычный контролируемый блок.

17. Перечислите возможные способы выхода из блока обработки.

  • выполняются все операторы обработчика, и происходит переход к
    оператору, расположенному после конструкции try...catch;
  • в обработчике выполняется оператор goto, break или continue. При этом по goto разрешается переходить на любой оператор вне конструкции try...catch, а внутрь контролируемого блока или в другую секцию-ловушку переход запрещен;
  • в обработчике выполняется оператор return, после чего происходит нормальный выход из функции;
  • в секции-ловушке выполняется оператор throw;
  • в обработчике генерируется другое исключение.

18. Каким образом исключение «передать дальше»?

Вызвать throw в секции-ловушке.

19. Сколько секций-ловушек должно быть задано в контролируемом блоке?

Сколько угодно.

20. Что такое «спецификация исключений»?

Часть заголовка функции (метода, конструктора или деструктора), явно указывающая набор генерируемых ею исключений. Если в заголовке функции не указана спецификация исключений, считается, что функция может порождать любое исключение.

21. Что происходит, если функция нарушает спецификацию исключений?

Если функция генерирует исключение, не заданное в спецификации исключений, то запускается стандартная функция unexpected(), которая вызывает функцию terminate(). Это инициирует аварийное завершение программы.

22. Учитывается ли спецификация исключений при перегрузке функций?

Функции с различными спецификациями исключений не считаются разными при перегрузке.

23. Отличается ли функция с «пустой» спецификацией throw() от функции, у которой отсутствует спецификация исключений?

Если указанные функции отличаются только спецификациями исключений, то – нет. В общем же случае функции с throw() запрещено генерировать исключения, в то время как функция без спецификации может генерировать любое исключение.

24. Что такое «стандартные» исключения? Назовите два-три типа стандартных исключений.

Иерархия класса exception и его наследников. Примеры: logic_error, runtime_error, bad_alloc.

25. Поясните «взаимоотношение» исключений и деструкторов.

Деструкторы должны разрабатываться так, как если бы они имели спецификацию исключения throw(), то есть все исключения, возникающие в деструкторе, должны быть перехвачены и обработаны там же.

26. Объясните, зачем может понадобиться подмена стандартных функций завершения.

Для подмены неперехваченных исключений исключением одного типа (bad_exception или заданного программистом).

27. Может ли конструктор генерировать исключение? А деструктор?

Да.

Да.

28. Какие виды нестандартных исключений вы знаете?

Структурная обработка исключений Windows (SEH, Structured Exception Handling), обработка исключений конкретной библиотеки (MFC, VCL).

29. В чем отличие механизма структурной обработки исключений Windows от стандартного механизма?

Не выполняется вызов деструкторов.

30. Какие стандартные функции можно подменить, и каким образом это делается?

unexpected(), terminate().

Подмена terminate() (для unexpected() выполняется аналогично):

set_terminate(f); // f функция с прототипом void f();

Глава 8. Наследование

1. Какие две роли исполняет наследование?

Наследование в ООП исполняет две роли: с одной стороны, предотвращает дублирование кода, с другой стороны, позволяет развивать работу в нужном направлении.

2. Какие виды наследования возможны в C++?

Простое и множественное, открытое и закрытое.

3. Чем отличается модификатор доступа protected от модификаторов private и public?

protected-элементы класса видны только прямым наследникам класса.

4. Чем открытое наследование отличается от закрытого и защищённого?

Открытое наследование не изменяет форму доступа в производном классе к элементам базового класса по сравнению с той, что объявлена в самом базовом классе. Защищённое наследование делает доступ к элементам базового класса защищённым, а приватное – приватным. Во всех случаях речь идёт о доступе к неприватным полям базового класса.

5. Может ли структура наследовать от класса? А класс от структуры?

Да.

Да.

6. Какой тип наследования от структуры реализуется по умолчанию? А от класса?

От структуры – открытое, от класса – закрытое. При этом общепринятой практикой является наследование только от классов.

7. В каких случаях в классе-наследнике недоступны элементы базового класса?

Если в базовом классе эти элементы объявлены как private.

8. Какие функции не наследуются?

Дружественные. Не наследуются также операция присваивания, конструкторы и деструктор.

9. Сформулируйте правила написания конструкторов в производном классе.

  • если в базовом классе нет конструкторов или есть конструктор без аргументов, то в производном классе будут созданы конструктор копирования и конструктор без аргументов;
  • если в базовом классе все конструкторы с аргументами, то производный класс должен иметь конструктор, в котором явно вызывается конструктор базового класса.

10. Каков порядок вызова конструкторов? А деструкторов?

При создании объекта производного класса сначала вызывается конструктор базового класса, затем — производного. Деструкторы вызываются в порядке, обратном порядку вызова конструкторов. Таким образом, создание и уничтожение объектов выполняется по принципу LIFO: «последним создан — первым уничтожен».

11. Если имя нового поля совпадает с именем унаследованного, то каким образом разрешить конфликт имен?

Поскольку новое поле скрывает поле базового класса, то для доступа к последнему используется квалификатор класса: Base::x.

12. Каким образом в конструкторе-наследнике вызвать конструктор базового класса?

В списке инициализации.

13. Что происходит, если имя метода-наследника совпадает с именем базового метода?

Метод производного класса скрывает одноимённый метод базового класса.

14. Каким образом в операции присваивания класса-наследника вызвать операцию присваивания базового класса?

С помощью квалификатора класса: Base::operator=(...);

15. Может ли вложенный класс наследовать от внешнего? А внешний от вложенного?

Да.

Да.

Ограничений в наследовании вложенных классов нет, но нужно следить за видимостью базового класса в точке наследования (см. с. 217).

16. Сформулируйте принцип подстановки.

При открытом наследовании объект производного класса является разновидностью объекта базового класса.

17. Когда выполняется понижающее приведение типов? А повышающее?

При преобразовании объекта базового класса в объект производного класса.

При преобразовании объекта производного класса в объект базового класса.

18. Объясните, что такое «срезка», или «расщепление».

Случай, когда в присваивании участвует два объекта из разных уровней одной иерархии. Одному из объектов присваивается только базовая часть (то, что он «знает») от другого объекта.

19. В каких случаях используется объявление using?

Использование using с именем метода/поля базового класса (using <имя_базового_класса>::<имя_в_базовом_классе>) позволяет открыть для клиента этот метод/поле при закрытом наследовании.

` class Base { public: void method() { cout << "method" << endl; } };

class Derived: private Base { public: using Base::method; };

int main() { Derived d; d.method(); } `

20. Какой вид наследования «ближе» к композиции: открытое или закрытое?

Закрытое.

Глава 9. Виртуальные функции

1. Объясните, зачем нужны виртуальные функции.

Для реализации динамического полиморфизма.

2. Что такое связывание?

Сопоставление имени функции с телом.

3. Чем раннее связывание отличается от позднего?

Раннее связывание выполняется на этапе трансляции, до запуска программы. Позднее связывание реализуется во время выполнения программы.

4. Какие два вида полиморфизма реализованы в C++?

  • статический (перегруженные функции, шаблоны);
  • динамический (виртуальные функции).

5. Влияет ли наличие виртуальных функций на размер класса?

Да. К размеру класса добавляется размер указателя (как правило, 4 байта) на таблицу виртуальных функций (Virtual Method Table).

6. Дайте определение полиморфного класса.

Класс, включающий в себя виртуальные функции, называется полиморфным.

7. Может ли виртуальная функция быть дружественной функцией класса?

Нет.

8. Наследуются ли виртуальные функции?

Да.

9. Каковы особенности вызова виртуальных функций в конструкторах и деструкторах?

Внутри конструкторов и деструкторов динамическое связывание не работает. Вызов виртуальных функций не запрещён, однако, вместо метода-наследника всегда вызывается «родной» метод.

10. Можно ли сделать виртуальной перегруженную операцию, например сложение?

Да.

11. Можно ли виртуальную функцию вызвать невиртуально?

Да, с помощью квалификатора класса.

12. Может ли конструктор быть виртуальным? А деструктор?

Нет.

Да.

13. В каких случаях вызов виртуальной функции класса-наследника через указатель базового класса требует явного преобразования типа?

  1. если через текущий указатель модификатор доступа не позволяет вызвать метод, а преобразование разрешит.
  2. если виртуальная функция начинает свою ветку не с базового класса, а с определённого типа с иерархии.

14. Как виртуальные функции влияют на размер класса?

Увеличивают его на размер указателя (как правило, 4 байта) на таблицу виртуальных функций (Virtual Method Table).

15. Как объявляется чистая виртуальная функция?

virtual тип имя(параметры) = 0;

16. Дайте определение абстрактного класса.

Класс, содержащий хотя бы одну чистую виртуальную функцию.

17. Наследуются ли чистые виртуальные функции?

Да.

18. Можно ли объявить деструктор чисто виртуальным?

Да.

19. Чем отличается чистый виртуальный деструктор от чистой виртуальной функции?

  • класс-наследник класса с чистым виртуальным деструктором не является абстрактным (поскольку деструкторы не наследуются, а при отсутствии явного определения автоматически создаются в производном классе);
  • помимо объявления чистого виртуального деструктора необходимо записать и его определение.

20. Зачем требуется определение чистого виртуального деструктора?

Из-за того, что деструктор производного класса обязательно вызывает деструктор базового класса.

21. Можно ли сделать операцию присваивания виртуальной? А операцию индексирования? А чистой виртуальной?

Да.

Да.

Да.

22. Объясните, как можно реализовать «виртуальность» независимой функции.

Для этого надо в базовом классе объявить чистый виртуальный метод и определить его в производных классах. Во внешнюю функцию через параметр передавать ссылку на элемент базового класса, а внутри функции вызвать определенный ранее метод от имени базового класса.

#include <iostream>

using namespace std;

class Control
{
public:
  virtual ostream& print(ostream &t) const = 0;
};

class Button: public Control
{
public:
  virtual ostream& print(ostream &t) const
  { return (t << "Button"); }
};

class ListBox: public Control
{
public:
  virtual ostream& print(ostream &t) const
  { return (t << "ListBox"); }
};

inline ostream& operator<<(ostream &t, const Control &r)
{ return r.print(t); }

int main()
{
  Button b;
  cout << b << endl;

  ListBox lb;
  cout << lb << endl;
}

23. Приведите классификацию целей наследования.

Автор приводит отрывок из книги Бадд Т. Объектно-ориентированное программирование в действии: Пер. с англ. - СПб.: Питер, 1997. Бадд считает, что порождение дочернего класса может быть выполнено по следующим причинам:

  • Специализация. Класс-наследник является специализированной формой родительского класса — в наследнике просто переопределяются методы. Принцип подстановки выполняется. Такая форма наследования в C++ реализуется простым открытым наследованием.
  • Спецификация. Класс-наследник реализует поведение, описанное в родительском классе. Ясно, что в C++ эта форма реализуется простым открытым наследованием от абстрактного класса.
  • Расширение. В класс-потомок добавляют новые методы, расширяя поведение родительского класса; принцип подстановки в такой форме выполняется.
  • Ограничение. Класс-наследник ограничивает поведение родительского класса. Очевидно, что в C++ такой вид наследования реализуется простым закрытым наследованием.
  • Конструирование. Класс-наследник использует методы базового класса, но не является его подтипом (принцип подстановки не выполняется). В C++ такую форму можно реализовать простым закрытым наследованием.
  • Варьирование. Базовый и производный классы являются вариациями на одну тему, однако связь «класс-подкласс» произвольна, например, «квадрат-прямоугольник» или «прямоугольник-квадрат». Эта форма фактически не отличается от «конструирования», так как класс-наследник, очевидно, «использует методы базового класса, но не является его подтипом».
  • Обобщение. Дочерний класс обобщает поведение базового класса. Обычно такое наследование требуется в тех случаях, когда мы не можем изменить поведение базового класса (например, базовый класс является библиотечным).
  • Комбинирование. Класс-наследник наследует черты нескольких классов — это множественное наследование.

24. Объясните разницу между наследованием интерфейса и наследованием реализации.

Наследование реализации — это закрытое наследование (интерфейс базового класса не доступен пользователям напрямую). Наследование интерфейса — открытое наследование от абстрактного класса (интерфейса).

25. Как связаны виртуальные функции и принцип подстановки?

Вместе они реализуют полиморфизм времени выполнения.

Глава 10. Множественное наследование и RTTI

1. Сколько классов может быть использовано в качестве базовых?

Сколько угодно.

2. Выполняется ли при множественном наследовании принцип подстановки?

При открытом множественном наследовании принцип подстановки выполняется.

3. Можно ли наследовать открыто от одного класса, а закрыто от другого?

Да.

4. В чем проблемы множественного наследования?

В неоднозначности полей и методов: какое именно из одноимённых полей/методов, унаследованных от базовых классов, использовать в классе-наследнике?

5. Объясните смысл виртуального наследования.

В последний производный класс при ромбовидном наследовании попадает только одна копия полей основного базового класса.

Пример:

class A { /*...*/ };
class B1: virtual public A { /*...*/ };
class B2: virtual public A { /*...*/ };
class D: public B1б public B2 { /*...*/ };
`

В класс D попадёт одна копия полей A, а не две, как при обычном наследовании.


### 6. Как формулируется принцип доминирования?


При виртуальном наследовании для разрешения неоднозначности имён компилятор выбирает поле (метод), определенное в ближайшем по иерархии наследования базовом классе.

Пример:

```C++
class Up
{ public: virtual ~Up() {};
  virtual void Print() { cout << "Up!" << endl; } };
class Left: virtual public Up
{ public: // метод Print переопределен
  virtual void Print() { cout << "Left!" << endl; } };
class Right: virtual public Up
{ // метод Print унаследован };
class Down: public Left, public Right
{ };

Класс Down наследует метод Print от класса Left.

7. Какие функции отвечают за инициализацию виртуального базового класса?

Виртуальный базовый класс инициализируется конструктором последнего производного (т. е. «самого производного») класса в иерархии.

В ромбовидной иерархии конструктор виртуального базового класса A нужно вызвать в конструкторе последнего производного класса D:

class A
{ int a;
public:
  A(const int &a): a(a) {} };

class B1: virtual public A
{ int b;
public:
  B1(const int &a, const int &b): A(a) { this->b = b; } };

class B2: virtual public A
{ int b;
public:
  B2(const int &a, const int &b): A(a) { this->b = b; } };

class D: public B1, public B2
{ double y;
public:
  D(int a, int b1, int b2, double y): A(a), B1(0,b1), B2(0,b2) { this->y = y; } };

8. Объясните принцип реализации «финального» класса.

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

class Lock
{ 
  Lock() { cout << "Lock" << endl; }
  Lock(const Lock&);
  friend class Final;
};

class Final: public virtual Lock
{ public:
  Final() { cout << "Final" << endl; }
};

Класс Final может получить доступ к конструктору «запирающего» класса Lock, так как Final является другом Lock, но любой класс, производный от Final, получить такого доступа не может, и поэтому нельзя создать объекты производных классов.

9. Какие конструкции входят в состав механизма RTTI?

  • оператор динамического преобразования типа dynamic_cast<>;
  • оператор идентификации точного типа объекта typeid();
  • класс type_info;

10. Можно ли выполнить понижающее приведение типа с помощью оператора dynamic_cast<>? А повышающее или перекрестное?

Преобразования с помощью dynamic_cast<> допускаются только между классами, входящими в одну иерархию наследования (между «родственниками»). Преобразование может быть:

  • повышающим — от производного класса к базовому;
  • понижающим — от базового класса к производному;
  • перекрестным — от одного производного класса к другому.

11. Чем различается работа оператора dynamic_cast<> при преобразовании указателей и ссылок?

Различается обработка аварийных случаев: для указателей — возвращается нулевой указатель, для ссылок — исключение bad_cast.

12. Какое исключение генерирует оператор dynamic_cast<> и в каких случаях?

При невозможности преобразования ссылки генерируется исключение bad_cast.

13. В каких случаях генерируется исключение bad_typeid?

Если аргументом оператора typeid() является указатель, то typeid() позволяет определить точный тип объекта, на который указывает указатель. Если указатель нулевой, то возбуждается исключение bad_typeid.

14. Дайте определение мультиметодов.

Мультиметод — это виртуальная функция, выбираемая при выполнении на основании типа нескольких аргументов. Мультиметоды могут понадобиться в ситуациях, когда требуется выполнять некоторую операцию с различными сочетаниями типов параметров.

15. Объясните, каким образом механизм RTTI может использоваться при реализации мультиметодов.

Создаётся базовый абстрактный класс, в котором определяется абстрактный метод — будущий мультиметод. Этот метод реализуется затем в производных классах. В реализациях проверяется правый операнд, и в зависимости от его типа выполняется та или иная обработка (или генерируется исключение).

Глава 11. Шаблоны классов

1. Для чего предназначены шаблоны?

Основное назначение шаблонов классов — реализация обобщенных контейнеров, не зависящих от типа элемента.

2. Какие виды шаблонов в C++ вы знаете?

Шаблоны классов и шаблоны функций.

3. Объясните термин «инстанцирование шаблона».

Процесс создания конкретного класса путём подстановки аргументов на место параметров шаблона.

4. В чем разница между определением и объявлением шаблона?

Объявления шаблона служат той же цели, что и объявления обычных классов: ввести имя шаблона в область видимости, чтобы на него можно было ссылаться. Объявление состоит из заголовка шаблона и не включает тело класса: template <параметры> class имя_класса;

5. Объясните назначение ключевого слова typename.

Чтобы заявить о том, что данный параметр шаблона — это тип.

6. Какие виды параметров разрешается задавать в шаблоне класса?

Основной вид параметров шаблона — имя типа. Кроме них параметром может быть:

  • объект целочисленного, символьного, булева или перечислимого типа;
  • указатель на объект или указатель на функцию;
  • ссылка на объект или ссылка на функцию;
  • указатель на член класса.

Ещё один вид параметров — параметры-шаблоны.

7. Можно ли параметрам шаблона присваивать значения по умолчанию?

Да.

8. Может ли параметром шаблона быть другой шаблон? Каковы особенности объявления параметра-шаблона?

Да.

В списке параметров параметра-шаблон на задаются имена.

9. Что такое специализация шаблона? Объясните разницу между полной и частичной специализацией.

Специализация заключается в том, что на основе исходного первичного шаблона реализуется его специализированная версия для некоторых конкретных значений параметров. Специализация называется полной, если конкретизированы все параметры первичного шаблона. Если определена только часть параметров, то специализация называется частичной.

10. Нужно ли задавать определение первичного шаблона для определения специализированных версий, или достаточно объявления?

Определение первичного шаблона задавать нужно.

11. Может ли шаблонный класс быть вложенным в другой шаблонный класс? А в обычный класс?

Да.

Да.

12. Можно ли объявить в классе шаблонный метод? А шаблонный конструктор?

Да.

Да.

13. Может ли шаблон класса быть наследником обычного класса? А обычный класс — наследником шаблона?

Да.

Нет. Только если базовый класс является полной специализацией или инстанцированным шаблоном.

14. Может ли вложенный класс быть шаблоном?

Да.

15. Можно ли обычный класс сделать внутренним классом шаблона? Существуют ли какие-нибудь ограничения в этом случае?

Да.

Нет.

16. Каким образом можно использовать возможность наследования обычного класса от шаблона?

Наследование класса от полной специализации шаблона позволяет организовать счетчик объектов для конкретного класса, а не для всей иерархии наследования. Причём реализовать эту функциональность можно один раз в базовом классе, а затем использовать в классах-наследниках.

17. Может ли шаблонный конструктор быть конструктором по умолчанию?

Нет.

18. Может ли шаблонный класс иметь «друзей»?

Да.

19. Какие проблемы возникают при объявлении дружественной функции для шаблонного класса?

При каждом инстанцировании шаблона создаётся новый экземпляр его дружественных функций, и, если функция не будет зависеть от параметров шаблонного класса, то при нескольких инстанцированиях сгенерируются одинаковые определения. Поэтому параметры дружественной функции должны зависеть от параметров шаблона.

20. Разрешается ли определять в шаблонном классе статические поля? А статические методы?

Да.

Да.

Глава 12. Шаблоны функций

1. Перечислите отличия шаблона функции от шаблона класса.

  1. в отличие от шаблона класса для шаблона функции нельзя задавать параметры по умолчанию.
  2. для шаблонов функций компилятор самостоятельно выводит подставляемый тип на основании информации о типе фактического аргумента при вызове (хотя и с некоторыми ограничениями).
  3. шаблон функции разрешается специализировать, но, в отличие от шаблонов класса, частичная специализация не разрешается.
  4. шаблон функции можно перегрузить как другим шаблоном, так и функцией.

2. Можно ли перегружать функцию-шаблон?

Да.

3. Какие параметры функции-шаблона выводятся автоматически?

Входные параметры.

4. Разрешается ли параметрам шаблона функции присваивать параметры по умолчанию?

Нет, но это ограничение можно обойти, использовав класс-оболочку и объявив функцию статическим методом этого класса.

5. Каким образом «обойти» ограничение частичной специализации для шаблонов функций?

«Обернуть» функцию в класс-шаблон и использовать частичную специализацию класса-шаблона.

6. Перечислите приёмы программирования, с помощью которых шаблон функции становится более универсальным.

Использование:

  • итераторов в качестве параметров обобщенных алгоритмов;
  • функторов для реализации аргументов-операций;
  • адаптеров (классов-оболочек для указателей на функции и указателей на методы).

7. Дайте определение функтора.

Функтор — объект, который ведет себя, как функция. Классом-функтором называют класс, в котором перегружена операция operator(). Таким образом функтор — это объект класса-функтора.

8. Можно ли операцию вызова функции реализовать как виртуальную функцию? А как статическую?

Да.

Нет.

9. Дайте определение функтора-предиката.

Функтор, который ведет себя как функция, возвращающая результат типа bool.

10. Чем отличается функтор с состоянием от обычного функтора?

Функтор с состоянием — это функтор, имеющий поля.

11. Можно ли указатель на функцию присвоить указателю на метод? А наоборот?

Нет.

Нет.

12. Отличается ли указатель на обычный метод от указателя на статический метод?

Да, поскольку нестатические методы получают дополнительный параметр — указатель this.

13. Может ли класс-функтор участвовать в иерархии наследования?

Да.

14. Объясните назначение адаптера-фиксатора.

Позволяет превратить бинарный функтор в унарный.

15. Можно ли перегружать операцию вызова функции?

Да.

Глава 13. Программы и модули

1. Назовите причины, требующие разделения программ на части.

Разбиение кода на части:

  • способствует инкапсуляции;
  • позволяет работать с разными частями разным программистам;
  • сокращает время трансляции программы (т. к. позволяет транслировать только изменившиеся модули а не всю программу).

2. Дайте определение термина «единица трансляции»?

Отдельный файл с исходным кодом, который получается после обработки препроцессором.

3. Чем отличается файл с исходным текстом от единицы трансляции?

Единица трансляции — это файл, обработанный препроцессором.

4. Существуют ли в C++ конструкции, позволяющие идентифицировать отдельный модуль?

Нет.

5. Какие способы сборки программы вы можете назвать?

  • сборка из единого исходного текста;
  • сборка из объектных модулей.

6. Что такое «объектный модуль»?

Это результат трансляции одного модуля.

7. Как называется программа, которая «собирает» объектные модули в программу?

Компоновщик = линковщик (linker).

8. В чем разница между аргументами "файл" и <файл> в директиве #include?

В первом случае файл вставляется из текущего каталога, во втором — из стандартного библиотечного каталога.

9. Что такое ODR?

Правило одного определения (One-Definition Rule, ODR): единица трансляции не должна содержать более одного определения любой переменной, функции, класса, перечисления и шаблона (в каждой области видимости, естественно).

10. Объясните, что такое «страж» включения и зачем он нужен.

Имя, с помощью которого препроцессор определяет, подключён данный файл или нет:

// myfile.hpp 
#ifndef MYFILE // страж определен? 
#define MYFILE // если нет - определяем 
// содержимое файла 
#endif // конец #ifndef

11. Является ли интерфейс класса его определением?

Да.

12. Сколько определений класса может быть в единице трансляции?

Одно.

13. Сколько определений класса может быть в программе из нескольких файлов?

Разрешается иметь несколько определений класса, но в разных единицах трансляции.

14. Чем различаются стандартные заголовки <string>, <string.h> и <cstring>?

<string.h> и <cstring> — разные способы подключения библиотеки для работы с символьными массивами, унаследованной из C. <string> — подключает стандартный класс C++ для работы со строками.

15. Каковы функции программы «мейкер»?

Мейкер (maker) — следит за изменениями исходных модулей. До тех пор пока мы не внесем изменения в код модуля, в проекте будет использоваться ранее оттранслированный объектный модуль.

16. Каким образом глобальную переменную, определенную в одной единице трансляции, сделать доступной в другой единице трансляции? А константу?

Объявить её имя как внешнее с помощью extern:

Определение Доступность
Переменная int i = 1; extern int i;
Константа extern const int i = 1; extern const int i;

17. Можно ли использовать слово extern при объявлении функций?

Да.

18. Как локализовать объявление функции в файле?

С помощью атрибута static или локального пространства имён (предпочтительный вариант).

19. Чем отличается «внешнее» связывание от «внутреннего»?

Внутреннее связывание (internal linkage) выполняется, когда объект локализован в одном модуле. Для объектов не локализованных в единственном модуле выполняется внешнее связывание (external linkage) — установка компоновщиком связи между объектом и обращениями к нему.

20. Что такое «спецификации компоновки»?

Нотация, позволяющая компоновать функции и библиотеки, написанные на разных языках программирования.

21. Для каких объектов по умолчанию характерно внутреннее связывание?

Для глобальных констант, неглобальных переменных.

22. Какие области видимости имен вы знаете?

Глобальная, в пределах единицы трансляции, в пределах класса, в пределах функции, в пределах тела цикла или условного оператора, в пределах прототипа функции.

23. Для чего используются пространства имен?

Для разрешения конфликтов имён в программе, разделённой на части.

24. Чем различаются именованные и неименованные пространства имён?

Наличием/отсутствием имени, анонимные пространства имен не «склеиваются».

25. Сколько неименованных пространств имен может быть в программе?

Одно глобальное и локальных анонимных — по числу единиц трансляции.

26. Что такое «глобальное пространство имён»?

Пространство имён, определенных на самом верхнем уровне, вне всех других пространств имён.

27. Могут ли пространства имён быть вложенными?

Да.

28. Для чего применяются синонимы в пространствах имен?

Для переопределения длинных имён.

29. Как сделать члены одного пространства имен доступными в нескольких (в пределе — во всех) файлах программного проекта?

С помощью объявления using:

using пространство_имен::имя_компонента; // один компонент
using namespace пространство_имен;       // все компоненты пространства имён

30. Объясните разницу между статической и динамической инициализацией.

Глобальные и статические переменные компилятор неявно инициализируются по умолчанию, причём до начала выполнения функции main(). Такая инициализация называется статической, в отличие от динамической, задаваемой программистом явно.

31. В чем состоит проблема инициализации глобальных статических переменных?

В том, что порядок инициализации неизвестен.

32. Для чего применяются директивы явного инстанцирования?

Для того, чтобы получить возможность использовать шаблон, разделённый на определение (заголовочный файл) и реализацию (cpp-файл).

33. Объясните, в чем состоят проблемы, возникающие при разделении шаблонного класса на интерфейс и реализацию?

Реализация шаблона не может быть вынесена в отдельный cpp-файл.

34. Что такое «модель явного инстанцирования» и как она работает?

Реализация шаблона вынесена в отдельный cpp-файл и явно указаны экземпляры шаблонов, которые нужно генерировать.

Для шаблона

template <typename T>
T summa(T const *begin, T const *end)

который будет применяться к строкам, директива явного инстанцирования может выглядеть так:

template string summa<string>(string const *begin, string const *end)

35. Какие проблемы вызывает внутреннее связывание в шаблонах?

Не позволяет использовать строковые литералы и указатели на них в качестве аргументов шаблона.

Глава 14. Библиотека ввода-вывода

1. Что такое «поток»? Дайте определение.

Поток — это последовательность символов.

2. Как классифицируются потоки, реализованные в библиотеках ввода-вывода C++?

Входные и выходные; форматируемые (при операциях ввода-вывода осуществляется преобразование информации) и неформатируемые; буферизуемые и небуферизуемые; широкие (wide, для широких символов wchar_t) и узкие (narrow).

3. Что такое буферизация и зачем она нужна?

Буферизация позволяет «развязать» программы и устройства. Вместо вывода непосредственно на устройство, вывод осуществляется в специально выделенную область памяти — буфер, а уже заполненный буфер выводится на устройство. При вводе программа также имеет дело с буфером, а не с устройством.

4. Какие библиотеки ввода-вывода реализованы в C++ и чем они различаются?

  • <cstdio> — процедурно-ориентированная, наследие С;
  • <iostream> — объектно-ориентированная.

5. Перечислите стандартные потоки и объясните их назначение.

  • stdin (стандартный ввод), stdout (стандартный вывод), stderr (стандартный поток ошибок) в <cstdio>;
  • cin (стандартный ввод), cout (стандартный вывод), clog (буферизованный стандартный поток ошибок), cerr (небуферизованный стандартный поток ошибок) в <iostream>.

6. Зачем нужен процесс форматирования и когда он выполняется?

Процесс форматирования — это преобразование информации при операциях ввода-вывода. Так как поток — это последовательность символов, то при вводе, как правило, выполняется преобразование данных из символьного вида в двоичное представление, а при выводе — наоборот.

7. Каковы особенности ввода строк?

Ввод строки выполняется до первого символа-разделителя (пробела). Для ввода строк с пробелами нужно воспользоваться методами getline() или get().

8. Каким образом ограничить набор вводимых символов при вводе?

Варианты:

  • get(буфер, длина_строки);
  • getline(буфер, длина_строки);

9. Объясните, для чего нужны строковые потоки. Почему строковые потоки — всегда форматируемые?

Строковые потоки — это средства перевода внутренних двоичных значений в строки (выходной строковый поток) и обратно (входной строковый поток). Поскольку форматирование и есть преобразование информации при операциях ввода-вывода, то строковые потоки всегда форматируемые.

10. Можно ли использовать тип string (и каким образом) со строковыми потоками?

Из string можно передавать информацию в строковый поток, в string можно сохранять итоговую строку.

11. Объясните, в чем различие между текстовым и двоичным файлами.

Двоичный файл — последовательность нулей и единиц, содержимое которой не подвергается преобразованию при вводе-выводе (т. е. двоичный файл не является форматируемым).

Текстовый файл является форматируемым, т. е. определенные комбинации нулей и единиц преобразуются в символы или наоборот. Один из символов (\n) имеет смысл "конца строки", поэтому говорят, что тестовый файл состоит из строк.

12. Объясните, что означает «открыть» файл и «закрыть» файл?

Открыть файл — означает установить связь между файлом и переменной-потоком. Закрыть файл — значит разорвать эту связь.

13. Каким образом внешний файл связывается с потоком?

Создаётся переменная — объект одного из классов-потоков.

14. Можно ли один и тот же поток связать с разными файлами? А один и тот же файл — с разными потоками?

Нельзя одновременно связать один и тот же поток с двумя файлами. Но можно делать это по очереди, закрыв один файл и открыв другой того же типа. Можно.

15. В каких случаях необходимо следить за ситуацией конца файла? Каким способом это делается?

При вводе данных, т. к. это событие может наступать только при чтении. Контроль осуществляется методом eof() объекта-потока.

16. Можно ли текстовый файл открыть как двоичный? А двоичный — как текстовый?

Да.

Да.

17. Какие операции и методы ввода-вывода требуются для обмена с текстовыми файлами?

Унаследовано от С: fprintf, fscanf.

Добавлено в С++: operator>>, operator<<, getline().

18. Перечислите методы ввода-вывода для работы с двоичными файлами.

read(), write().

19. Объясните, что означает «перенаправление» потока? Какие потоки можно перенаправлять и куда?

Вывод данных в иной поток, нежели предполагалось вначале.

Любой в любой.

20. В чем преимущества объектно-ориентированной библиотеки ввода-вывода по сравнению с процедурной?

Обеспечение контроля типов; общий стиль для вывода как встроенных, так и пользовательских типов; объектно-ориентированную библиотеку удобнее расширять.

21. В каких состояниях может находиться поток? Каким образом отслеживается состояние конца потока?

good, bad, fail, eof.

С помощью метода eof().

22. Каким образом строковые потоки можно использовать для ограничения ширины поля ввода?

Установить ширину поля ввода с помощью width(), считать установленное количество символов в строковый поток, и сохранить результат в целевой переменной.

23. Перечислите средства форматирования объектно-ориентированной библиотеки.

Флаги форматирования, методы форматирования, манипуляторы.

24. Каким образом ввести строку типа string с пробелами?

getline(), get().

25. Каково назначение флагов форматирования? Какие средства реализованы в библиотеке для работы с флагами форматирования?

Флаги предназначены для задания режимов работы системы ввода-вывода.

Методы для работы с флагами:

fmtflags flags() const;         // получить флаги
fmtflags flags(fmtflags flags); // получить и установить флаги (с предв. сбросом)
fmtflags setf(fmtflags flag);   // получить и установить флаги
void unsetf(fmtflags flags);    // сбросить флаги

Кроме того, флаги устанавливаются/сбрасываются манипуляторами форматирования.

26. Что такое «манипулятор»? В чем преимущества манипуляторов перед флагами форматирования?

Манипулятор — это операция, управляющая состоянием потока данных непосредственно во время операции ввода-вывода.

Манипуляторы задаются непосредственно в операции ввода-вывода в том месте, где необходимы.

27. Как связываются файлы с потоками в объектно-ориентированной библиотеке?

Через конструктор и деструктор или открытие/закрытие.

28. Можно ли файлы, записанные независимо от C++, прочитать объектно-ориентированными средствами ввода-вывода C++? А наоборот?

Если под файлами, записанными независимо от С++, понимаются файлы, записанные средствами библиотеки , то — да, можно.

Да, можно.

29. Перечислите режимы открытия объектно-ориентированных файловых потоков. Каким образом комбинируются режимы открытия файловых потоков?

  • in — открытие потока для чтения;
  • out — открытие потока для записи;
  • trunc — удаление прежнего содержимого файла
  • арр - открытие потока для записи в конец файла;
  • ate — открытие потока для чтения и (или) записи и позиционирование в конец файла;
  • binary — открытие потока в двоичном режиме (по умолчанию режим текстовый).

Допустимые комбинации флагов открытия:

Комбинация флагов Описание
in чтение
out стирание и запись
out|trunc стирание и запись
арр дозапись
out|app дозапись
in|out чтение и запись
in|out|trunc стирание, чтение и запись

В любую из указанных комбинаций можно добавить флаги binary и (или) ate.

30. Обязательно ли закрывать файл, связанный с объектно-ориентированным файловым потоком? А открывать?

В обоих случаях — необязательно. Достаточно создать объект класса потока, указав параметры конструктора. При уничтожении объекта всю работу выполнит деструктор.

31. Каким образом открыть файловый поток для чтения и записи одновременно?

Использовать комбинацию режимов in|out. Например:

fstream stream("/path_to_file", std::ios::in|std::ios::out);

32. Как открыть файловый поток для дозаписи?

Использовать режим app:

ofstream out("/path_to_file", std::ios::app);

33. Можно ли вывести значение переменной в двоичном виде и как это сделать?

Можно. Например, с помощью подобного манипулятора:

class binary 
{ unsigned long k; 
public: 
  binary(unsigned long k): k(k) {} 
  friend ostream& operator<<(ostream& os, const binary& t); 
};

inline ostream& operator<<(ostream& os, const binary& t) 
{ const unsigned long MAX = numeric_limits<unsigned long>::max(); 
  unsigned long bit = -(MAX >> 1);
  while(bit) 
  { os << (t.k & bit ? '1' : '0'); bit >>= 1; } 
  return os; 
}

34. Разрешается ли наследовать от классов библиотеки ввода-вывода?

Да.

35. Каким образом можно перенаправить объектно-ориентированный поток?

С помощью метода rdbuf(). Например, перенаправление стандартного потока вывода в файл выглядит так:

ofstream file("/path_to_file/out.txt");
streambuf *b = cout.rdbuf();     // сохраняем указатель на буфер cout
cout.rdbuf(file.rdbuf());        // перенаправим вывод cout->file
cout << "Output string" << endl;
cout.rdbuf(b);                   // восстановим буфер cout

36. Как используется буфер потока для копирования потока?

Содержимое буфера потока-источника выводится в поток-цель:

istream src;
ostream dst;
dst << src.rdbuf();

37. Какими операциями выполняется форматированный ввод в файловые потоки и вывод из них? А неформатированный?

  • форматированный: fprintf, fscanf (С); operator<<, operator>> (C++)
  • неформатированный: fread, fwrite (С); read, write (C++, хотя, вообще говоря, это методы)

38. Реализованы ли в объектно-ориентированной библиотеке средства прямого доступа к файловым потокам?

Да.

39. С какими объектно-ориентированными потоками разрешается, а с какими не разрешается использовать средства прямого доступа?

Если под средствами прямого доступа понимаются средства доступа к произвольной позиции в потоке, то позиционирование можно выполнять в любых потоках, кроме стандартных.

40. Покажите, каким образом можно выполнить перегрузку операций ввода-вывода для нового типа данных.

class A
{
private:
  int data;
public:
  A(int i): data(i) {};
  int get() { return data; }
  void set(int i) { this->data = i; }
};

std::ostream& A::operator<<(std::ostream& out, const A &a)
{ out << a.get(); return out; }

std::istream& A::operator>>(std::istream& in, A &a)
{ int i = 0; in >> i; a.set(i); return in; }

41. Как выполняется обработка ошибок ввода-вывода в объектно-ориентированной библиотеке?

С помощью флагов состояния потока. По умолчанию при ошибках ввода-вывода исключения не генерируются, однако можно указать, при установке каких флагов состояния должно генерироваться исключение.

42. Какое стандартное исключение генерируется при ошибках ввода-вывода? Обязательно ли оно генерируется?

ios_base::failure

Нет, не обязательно. Необходимо указать, при установке каких флагов будет генерироваться исключение. Например, указать на необходимость генерации исключения при установке флага ios::badbit можно так:

stream.exceptions(ios::badbit);

43. Чем стандартные широкие потоки отличаются от узких?

Широкие (wide) потоки предназначены для работы с широкими символами wchar_t, а узкие — с символами char.

44. Что такое «локальный контекст», и каково его назначение?

Локальный контекст — это объект, инкапсулирующий национальные стандарты внешнего представления формата чисел, даты, денежных величин и т. п. Основное назначение локального контекста — интернационализация программы.

45. Как установить русскоязычный шрифт при выводе в консольное окно?

Для вывода широких русскоязычных строковых констант нужно установить русский локальный контекст для потока wcout.

46. Чем отличается ввод-вывод широких файловых потоков от узких?

На уровне интерфейса — только обозначениями классов-потоков.

Благодарности

Спасибо пользователю Oleg K за обнаруженные ошибки и советы по их исправлению.



Комментарии

comments powered by Disqus