Skip to content

vyragosa/OOP-exam-questions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 

Repository files navigation

OOP-exam-questions

Вопросы к экзамену по ООП

1 Аргументы, передаваемые функции по умолчанию

В C++ мы можем придать параметру некоторое значение, которое будет автоматически использовано, если при вызове функции не задается аргумент, соответствующий этому параметру. Аргументы, передаваемые функции по умолчанию, можно использовать, чтобы упростить обращение к сложным функциям, а также в качестве "сокращенной формы" перегрузки функций.

void myfunc(double num = 0.0, char ch = 'Х')
{
   ///
}

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

myFunc(198.234, 'A'); // Передаем явно заданные значения.
myfunc(10.1); // Передаем для параметра num значение 10.1, а для параметра ch позволяем применить аргумент, задаваемый по умолчанию ('Х').
myfunc(); // Для обоих параметров num и ch позволяем применить аргументы, задаваемые по умолчанию.

При первом вызове параметру num передается значение 198.234, а параметру ch — символ 'А'. Во время второго вызова параметру num передается значение 10.1, а параметр ch по умолчанию устанавливается равным символу 'Х'. Наконец, в результате третьего вызова как параметр num, так и параметр ch по умолчанию устанавливаются равными значениям, заданным в объявлении функции.

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

2. Архитектура системы. Иерархия объектов

untitled

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

Наследование поддерживает иерархические отношения между классами – is a. Основой выявления таких отношений, является исследование задачи, проведение классификации выявленных сущностей – классов, определения их общих свойств и поведения. На верхний уровень иерархии выносятся свойства и операции общие для всех сущностей, далее уровни выстраиваются по такому же принципу.

untitled

3. Ввод-вывод в С++. Потоки

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

В C++ для ввода и вывода используются потоки cin и cout.

4. В чем различие между ссылкой и указателем?

untitled untitled

Чтобы использовать свойства и методы через указатель на объект, необходимо использовать -> вместо .

class MyClass {
public:
  void foo();
};

int main() {
  MyClass obj;
  MyClass obj_ptr = &obj;
  obj_ptr->foo();
}

В Visual C++ (CLR – Common Language Runtime) поддерживаются три типа указателей:

  • управляемые указатели (managed pointers);
  • неуправляемые указатели (unmanaged pointers);
  • неуправляемые указатели на функции (unmanaged function pointers).

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

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

Управляемые указатели – это указатели ссылочного типа. Эти указатели передаются для аргументов, методов, которые передаются по ссылке.

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

В таких указателях вместо символа ‘*’ применяется символ ‘^’. Для выделения.

Неуправляемые указатели (unmanaged pointer) – это традиционные указатели C/C++. Они являются указателями на объекты в неуправляемом объеме памяти, которая выделяется для выполнения программы. Неуправляемые указатели не являются совместимыми со спецификацией CLR.

Неуправляемые указатели на функции – это указатели на функции, которые можно обрабатывать таким же образом, как и неуправляемые указатели на объекты (данные).

Чтобы использовать свойства и методы через указатель на объект, необходимо использовать -> вместо.

5. Виртуальные базовые классы

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

В качестве примера, рассмотрим следующую задачу. Класс ChildClass является наследником классов ParentClass1 и ParentClass2.

class BaseClass {
public:
  int num;
};

class ParentClass1 : public BaseClass{
  
};

class ParentClass2 : public BaseClass{
  
};

class ChildClass : public ParentClass1, public  ParentClass2 {
};

int main() {
   ChildClass obj;
   obj.num = 1;
   // Ошибка! неявное имя переменной
   return 0;
}

Программу можно исправить двумя способами:

  1. Применить оператор разрешения области видимости :: к переменной num
class BaseClass {
public:
  int num;
};

class ParentClass1 : public BaseClass{
  
};

class ParentClass2 : public BaseClass{
  
};

class ChildClass : public ParentClass1, public  ParentClass2 {
};

int main() {
   ChildClass obj;
   obj.ParentClass1::num = 1;
   return 0;
}

Оператор разрешения области видимости :: позволяет явно выбрать вариант производного класса. Однако данный способ решения порождает проблемы: Что если на самом деле нужна лишь одна копия объекта, можно ли предот­вратить дублирование объектов?

  1. Использование виртуальных базовых классов
class BaseClass {
public:
  int num;
};

class ParentClass1 : virtual public BaseClass {
  
};

class ParentClass2 : virtual public BaseClass {
  
};

class ChildClass : public ParentClass1, public  ParentClass2 {
};

int main() {
   ChildClass obj;
   obj.num = 1;
   return 0;
}

Как видим, перед именем базового класса в спецификации производного класса стоит ключевое слово virtual.

Теперь оба класса являются наследниками виртуального базового класса BaseClass, и любые их наследники будут со­ держать лишь одну его копию. Следовательно, выражение obj.num = 1 становится однозначным

6. Взаимодействие объектов. Три примера взаимодействия объектов

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

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

Например, он может:

  • опустить монету в автомат по продаже газет;
  • нарисовать круг или квадрат;
  • вычислить их площадь или периметр;

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

untitled

7. Виртуальные методы. Наследование виртуальных методов

Виртуальная функция — функция-член, объявленная в базовом классе и переопределенная в производном.

Чтобы создать виртуальную функ­цию, следует указать ключевое слово virtual перед ее объявлением в базовом классе.

По существу, виртуальная функция реализует принцип “один интер­фейс, несколько методов”, лежащий в основе полиморфизма.

При наследовании виртуальной функции ее виртуальная природа также наследует­ся.

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

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

8. В чем отличие между классом и структурой?

В С++ основная разница между структурой и классом - это модификатор доступа, который используется по умолчанию для их членов. Для классов, по умолчанию используется модификатор private, а для структур - public.

9. Встраиваемая функция

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

Такие функции можно определить в классе, а можно объявить в классе как обычную функцию – член, а реализовать вне класса как функцию inline.

Тела функций встраиваются в код на месте вызова этой функции при выполнении препроцессорной обработки

inline void foo() {
  // тело функции
}

10. Для чего используется ключевое слово protected?

Модификатор доступа protected (защищённый) разрешает доступ самому классу, наследующим его классам и родительским классам.

11. Дружественная функция

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

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

Правила использования:

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

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

friend void foo();

12. Дружественный класс

Класс можно объявить дружественным по отношению к другому классу при ключевого слова friend. В таком случае в классе можно будет получить доступ к приватным и защищённым свойствам и методам другого класса.

class MyClass1 {
friend class MyClass2;
}

13. Защищенные члены класса

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

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

class MyClass {
protected:
  //защищенные члены класса
};

14. Жизненный цикл объекта

untitled

15. Жизненный цикл виртуального объекта и его реализация на языке С++

untitled

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

Три правила полиморфизма, начинают работать правильно, связывают объект и метод на этапе выполнения правильно:

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

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

  • Виртуальная функция остается таковой во всех производных классах. Если она в каком-то классе не переопределена, то механизм виртуальных функций в этом классе сохраняется. Виртуальная функция может быть дружественной другому классу. При реализации виртуальной функции наследниками слово virtual не указывается.

16. Инкапсуляция

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

Для обеспечения скрытия данных и поддержки инкапсуляции в классах С++ используются уровни доступа к членам класса:

  • private - список полей и методов объявленных после спецификатора private будет доступен только методам этого класса и друзьям.
  • public - члены класса доступные другим функциям и объектам программы Методы этой части класса представляют интерфейс класса.
  • protected – члены класса доступные в классе, наследникам, друзьям.

17. Имеются два способа сделать функцию встраиваемой. Что это за способы?

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

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

#define SUMMA(a, b) a + b
rez = SUMMA(x, y)*10;

После работы препроцессора получим:

rez = x + y*10;

В С++ для определения функции, которая должна встраиваться как макроопределение используется ключевое слово inline. Вызов такой функции приводит к встраиванию кода inline-функции в вызывающую программу. Определение такой функции может выглядеть следующим образом:

inline double SUMMA(double a, double b)
{
  return(a + b);
}

При вызове этой функции:

rez = SUMMA(x,y)*10;

будет получен следующий результат:

rez=(x+y)*10;

При определении и использовании встраиваемых функций необходимо придерживаться следующих правил:

  • Определение и объявление функций должны быть совмещены и располагаться перед первым вызовом встраиваемой функции.
  • Имеет смысл определять inline только очень небольшие функции, поскольку любая inline-функция увеличивает программный код.
  • Различные компиляторы накладывают ограничения на сложность встраиваемых функций. Компилятор сам решает, может ли функция быть встраиваемой. Если функция не может быть встраиваемой, компилятор рассматривает ее как обычную функцию.

Таким образом, использование ключевого слова inline для встраиваемых функций и ключевого слова const для определения констант позволяют практически исключить директиву препроцессора #define из употребления.

18. Исключительные ситуации

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

Существует два вида исключений:

  • Аппаратные (структурные, SE-Structured Exception), которые генерируются процессором. К ним относятся, например,
    • деление на 0;
    • выход за границы массива; обращение к невыделенной памяти
    • переполнение разрядной сетки.
  • Программные, генерируемые операционной системой и прикладными программами – возникают тогда, когда программа их явно инициирует. Когда встречается аномальная ситуация, та часть программы, которая ее обнаружила, может сгенерировать, или возбудить, исключение.

Оператор throw позволяет сгенерировать исключительную ситуацию.

struct MyException
{
    MyException(const char* message): _message(message) {}
    const char* what() const
    {
        return _message;
    }
private:
    const char* _message;
};

int main()
{
    try {
        throw MyException("foobar");
    } catch (const MyException& e)
    {
       std::cout << "Caught custom exception!" << std::endl
        << e.what() << std::endl; 
    }
    return 0;
}

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

Синтаксис try/catch

try {
    // ...
}
catch (const typeone& arg) {
    // ...
}
catch (const typetwo& arg) {
    // ...
}
// ...
catch (const typeN& arg) {
    // ...
}

блок обрабатывает все ошибки (можно добавлять после блоков catch, обрабатывающих какие-либо типы аргументов:

try {
    // ...
}
catch (...) {
    // ... handles any errors or unhandled errors
}

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


Ограничение исключительных ситуаций

Можно ограничить типы исключительных ситуаций, которые может генерировать функция:

template <typename T>
class MyArray
{
    T* arr;
    size_t capacity = 5;
    size_t cur_size = 2;
public:
    T& operator[](size_t pos) const throw(IndexOutOfRangeException);
};

template <typename T>
T& MyArray<T>::operator[](size_t pos) const throw(IndexOutOfRangeException) {
    if (pos >= cur_size)
        throw IndexOutOfRangeException("");
    throw "";
    return arr[pos];
}

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

Если же этой ошибки не возникнет, перегруженный оператор [] сгенерирует неожиданное исключение типа const char*, из-за которого, вне зависимости от обработки исключений, программа аварийно завершится ( на самом деле попытка сгенерировать исключительную ситуацию, не поддерживаемую функ­цией, сопровождается вызовом стандартной функции unexpected(). Затем по умолчанию вызывается функция abort(), и программа завершается аварийно.).

Если необходимо запретить функции вообще генерировать любые исключительные си­туации, список типов следует оставить пустым:

template<typename T>
void MyArray<T>::print_array() const throw() {
    for (size_t i = 0; size_t < capacity; i++)
    {
        if (i)
            std::cout << " ";
        std::cout << arr[i];
    }
    std::cout << std::endl;
}

19. Класс. Назначение и синтаксис описания

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

Структура класса включает две части:

  • интерфейс – внешнее устройство класса - операции доступные всем объектам системы – обязательства класса перед своими клиентами. Главное в интерфейсе – объявление операций, поддерживаемых всеми экземплярами класса.

  • реализация – внутреннее устройство класса – реализация операций объявленных в интерфейсной части.

  • Описание заголовочной части класса

class MyClass {
private:
//список скрытых элементов класса
public:
MyСlass(); // конструктор
//список доступных элементов класса
~MyClass(); //деструктор
protected:
//список защищенных элементов класса
}; 
  • Описание части реализации класса
void MyClass::foo() {
    // тело метода (код алгоритма метода) 
}

20. Класс vector

untitled untitled

std::vector<T> — динамический массив.

  • begin, end, rbegin, rend cbegin, cend, crbegin, crend
  • push_back, pop_back
  • at(size_t i) — возвращает ссылку на i-ый элемент
  • back — возвращает ссылку на последний элемент
  • size_t capacity() const — возвращает число элементов, которое вектор может содержать без выделения дополнительного пространства.
  • size() — Возвращает количество элементов в векторе.
  • clear()
  • empty()
  • erase() — Удаляет элемент или диапазон элементов в векторе из заданных позиций. итератор или итераторы begin-end [it_beg; it_end)
  • insert() — вставляет элемент или несколько элементов или диапазон элементов в указанную позиции в вектор.

21. Класс string

В С для представления строк используется строка, завершающаяся нулем. Эта строка используется и в С++. В С++ применяется еще и класс String.

Этот тип строк - это объект шаблонного класса basic_string.

Строка string из <string> это производный тип от basic_string, а wstring – широкая строка (Unicode). Инкапсулирует последовательность символов.

Строка является динамической, растет по мере добавления символов.

Количество символов, которые будет содержать строка, не является предопределенным. Объект класса string будет расти по мере необходимости, чтобы вместить строку, которую эта переменная должна хранить. При создании строковой переменной ей выделяется первоначальный объем памяти, чтобы сохранить значение строки. Используя метод capacity() можно определить размер необходимо памяти. При выполнении операций добавления, если в переменной недостаточно места, размер автоматически увеличивается на 16 байт при каждом расширении.

std::cin >> s1; //до разделителя и \n и \n не читает
getline(cin, s1); //читает всю строку
  • size()/length()
  • c_str() — возвращает строку, преобразованную в C-строку
  • starts_with()/ends_with()
  • std::string substr(size_t offset = 0, size_t count = npos) — Копирует из указанного положения в строке подстроку, содержащую по крайней мере несколько символов
std::string s = "Hello, World!123 12 3123 123 123 3 123123 123";
    std::cout << s.substr(0, 13) << std::endl; // Hello, World!
  • find/rfind
std::string s = "Hello, World!123 12 3123 123 123 3 123123 123";

size_t pos = s.find("World");
size_t pos_end = s.find_last_of("World");

std::cout << pos << " "  << pos_end << std::endl; // 7 11
  • copy(char* c, size_t n, size_t offset = 0) const — копирует часть строки в c
  • replace() — заменяет элементы в строке в указанной позиции заданными символами или символами, скопированными из других диапазонов, строк или C-строк.
  • append() - добавляет символы в конец строки
  • erase() - удаляет элемент или диапазон элементов с указанного положения в строке
iterator erase(
    iterator first,
    iterator last);

iterator erase(
    iterator iter);

basic_string<CharType, Traits, Allocator>& erase(
    size_type offset = 0,
    size_type count = npos);
  • find_last_of — Выполняет в строке поиск последнего символа, совпадающего с любым элементом заданной строки.
  • capacity()
  • static const size_type npos = -1 — целочисленное значение без знака, инициализированное значением -1, которое указывает на отсутствие найденных или всех оставшихся символов при сбое функции поиска.

22. Какая инструкция catch перехватывает все типы исключительных ситуаций?

Бывают случаи, когда нужно перехватить все исключительные ситуации подряд. Для этого, в C++ используется блок catch(…), который имеет следующую общую форму

catch(...)
{
  // Обработка всех исключительных ситуаций
  // ...
}

23. Какова основная форма конструктора копий?

MyClass(int a); // конструктор копирования

24. Конструктор копии

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

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

Такой конструктор имеет один параметр – ссылку на объект своего класса.

class MyClass {
  int a;
public:
  MyClass(int a); // конструктор копирования
};

MyClass::MyClass(int a) {
  this->a = a;
}

25. Контейнеры и итераторы

untitled untitled

Основные контейнеры:

  • std::vector
  • std::list
  • std::set
  • std::map
  • std::queue

Итераторы STL-контейнеров:

  • begin, end, rbegin, rend
  • cbegin, cend, crbegin, crend

26. Контейнер – динамический массив

Это есть std::vector

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

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

Стандартное присваивание можно переопределить, перегрузив оператор присваивания для конкретного класса.

28. Контейнер – ассоциативный список

Есть std::map

29. Какой тип операций ведет к вызову конструктора копий?

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

//Прототип конструктора копирования
Class(const Class &);

30. Конструктор и деструктор объекта

Конструктор класса - это метод, обеспечивающие создание(инициализацию полей) экземпляров класса (объектов). Объект это переменная класса.

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

Виды конструкторов:

Виды конструкторов:

  • Конструктор по умолчанию. Если программист не включил в класс ни одного конструктора, то компилятор создаст его автоматически, и он будет вызывается автоматически при определении объекта.
  • Явно определенный конструктор. Такой конструктор реализуется по формату функции, его имя – это имя класса, эта функция не имеет типа.
    • Без параметров – его называют конструктором по умолчанию.
    • С параметрами – создает объект и инициализирует члены данных значениями параметров. В классе может быть несколько перегруженных конструкторов с различным количеством параметров. Параметры могут быть любого типа, кроме типа этого класса.
  • Конструктор копирования. Такой конструктор реализуется по формату функции, его имя – это имя класса, эта функция не имеет типа, даже void.
    • Стандартный конструктор копирования – осуществляет поэлементное копирование однотипных объектов. Вызывается при присваивании объектов одного типа.
    • Не стандартный конструктор копирования – (создается программистом) включается в класс, в котором есть динамически создаваемые члены данных. Такой конструктор имеет один параметр – ссылку на объект своего класса.
class MyClass {
public:
  MyClass(); // конструктор
};

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

Виды деструкторов:

  • Деструктор по умолчанию. Вызывается автоматически, когда объект выходит из области видимости. Видимость объектов определяется по правилам видимости локальных, глобальных, статических переменных.
  • Явно определенный деструктор. Требуется, если среди членов данных имеются динамические переменные, которые удаляются другими средствами.
class MyClass {
public:
  ~MyClass(); // деструктор
};

31. Класс map и multimap

untitled untitled untitled untitled

std::map<K, V> — ассоциативный массив, хранящий значения типа V по ключам типа K.

  • те же методы, что и у multimap

std::multimap<K, V>мультиотображение — множество, в которое каждый элемент может входить несколько раз

#include <map>
#define int_pair std::pair<int, int>

template <typename K, typename V>
void show(const std::multimap<K, V>& mm)
{
    for (typename std::multimap<K, V>::const_iterator it = mm.begin(); it != mm.end(); it++)
        std::cout << it->first << " " << it->second << std::endl;
}
  • multimap::begin(), multimap::end() — возвращает итератор, адресующий первый элемент в мультиотображении.
  • rbegin, rend
  • multimap::cbegin(), multimap::cend() — возвращает константный итератор, адресующий первый элемент в мультиотображении.
  • crbegin, crend
  • multimap::insert(std::pair) — вставляет в мультиотображение пару ключ-значение, представляемую парой std::pair<K, V>.
  • multimap::count(const K&) — возвращает число элементов в мультиотображении, ключи которых совпадают с ключом, заданным параметром.
  • bool multimap::contains(const &K) — проверяет, существует ли элемент с указанным ключом в multimap
  • multimap::empty() — проверяет, что мультиотображение пусто — возвращает true, если multimap пусто; false если multimap не является пустым.
  • multimap::clear() — удаляет все элементы мультиотображения
  • multimap::size() — возвращает размер мультиотображения
  • multimap::find(const K&) — возвращает итератор, ссылающийся на элемент в мультикарте, ключ которого эквивалентен заданному ключу.
  • multimap::erase(const_iterator Where)/multimap::erase(const_iterator First, const_iterator Second)/multimap::erase(const K&)
  • multimap::equal_range — Находит диапазон элементов, где ключ элемента соответствует заданному значению.
pair<const_iterator, const_iterator> equal_range (const Key& key) const;

pair<iterator, iterator> equal_range (const Key& key);

32. Можно ли адрес объекта передать функции в качестве аргумента?

Передача аргумента по адресу включает в себя передачу не самой переменной аргумента, а ее адреса.

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

33. Множественное наследование

Множественное наследование - это создание нового класса на основе нескольких существующих классов.

class ParentClass1 {
  // тело класса
};

class ParentClass2 {
  // тело класса
};

class ChildClass: public /* protected | private*/ ParentClass1, public /* protected | private*/  ParentClass2 {
  // тело класса
};

34. Можно ли использовать инструкцию throw, если ход выполнения программы не затрагивает инструкции, расположенные в блоке try?

Фактически обработка исключений – это работа блока (ов) catch. Ключевое слово catch используется для определения блока кода (называемого блоком catch), который обрабатывает исключения для одного типа данных.

Вот пример блока catch, который перехватывает исключения со значениями int:

catch (int x)
{
    // Здесь обрабатываем исключение типа int
    std::cerr << "We caught an int exception with value" << x << '\n';
}

Блоки try и блоки catch работают вместе – блок try обнаруживает любые исключения, которые вызываются инструкциями в блоке try, и направляет их для обработки в соответствующий блок catch. Блок try должен иметь сразу после себя, по крайней мере, один блок catch, но он также может иметь несколько блоков catch, идущих последовательно.

Как только исключение было перехвачено блоком try и направлено в блок catch для обработки, исключение считается обработанным, и выполнение возобновится в обычном режиме после блока catch.

35. Может ли быть инициализирован массив, память для которого выделяется динамически?

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

int *p; // указатель на тип int

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

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

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

36. Наследование. Реализация наследования на языке С++

Наследование – механизм создания нового класса на основе уже существующего.

Наследование - возможность создания производных классов (классов наследников), взяв за основу методы и элементы базового класса (класса родителя).

Язык С++ поддерживает реализацию двух видов наследования:

  • Простое, новый класс формируется на основе одного существующего класса
  • Множественное, создание нового класса на основе нескольких существующих классов
class ChildClass : public /* private | protected */  ParentClass {
    // тело класса
};

37. Объявление элементов класса спецификацией static

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

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

class Shared
{
    static int n;
    int b;
public:
    void increment() const;
    static void show();
};

int Shared::n; // int Shared::n = 0;
void Shared::increment() const
{
    Shared::n++;
}
void Shared::show()
{
    std::cout << Shared::n << std::endl;
}

int main()
{
    Shared o;
    Shared::show();
    o.increment();
    Shared::show();

    return 0;
}
class Counter
{
public:
    static int count;
    Counter() { count++; }
    ~Counter() { count--; }
};

int Counter::count = 0;

38. Объявление элементов класса спецификацией const

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

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

class Counter
{
public:
    static const int count;
};

const int Counter::count = 15;

Если объявить метод-член класса со спецификацией const, в методе нельзя будет изменить значения полей класса или вызвать методы, делающие это:

class Counter
{
    int count;
public:
    Counter(int n = 0): count(n) {}
    int increment() { return ++count; }
    void show() const { std::cout << count << std::endl; }
    ~Counter() {}
};

39. Объявление объекта и доступ к его элементам

Чтобы объявить объект, необходимо указать класс/тип объекта, а затем имя объекта.

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

class MyClass {
public:
  void foo();
};

int main() {
  MyClass obj; // объявление объекта
  obj.foo(); // вызов метода объекта
}

40. Объединение. Назначение и синтаксис описания

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

Для определения объединений применяется ключевое слово union и следующий формальный синтаксис:

union _name //имя_объединения
{
    //определения_элементов
};

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

Так, создадим простейшее объединение:

union code
{
    int digit;
    char letter;
};

Объединение code хранит в одном и том же участки памяти объект int и объект char. Объединение сode на большинстве платформ будет занимать 4 байта. Длина элементов, как здесь, может быть разной, и в этом случае размер объединения вычисляется по наибольшему элементу.

41. Объекты в качестве возвращаемого значения функции

Когда функция возвращает объект, автоматически создается временный объект, содержащий возвращаемое значение. Именно этот объект фактически возвращается функцией. После того, как значение возвращено, этот объект уничтожается.

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

42. Определение адреса перегруженной функции

Функция имеет адрес. Этот адрес можно присвоить указателю, а затем вызывать функцию не по имени, а через ее указатель.

Если функция myfunc() не перегружена, она существует в одном экземпляре, и компи­лятор без труда вычисляет ее адрес. Однако, если функция myfunc() перегружена, воз­никает вопрос, каким образом компилятор может вычислить ее указатель? Ответ зави­сит от того, как объявлен указатель р.

int myfunc(int);
int myfunc(int, int);

int main()
{
    int (*fp)(int, int);
    
    fp = myfunc;

    std::cout << fp(5, 3) << std::endl; // 15

    return 0;
}

int myfunc(int a)
{
    return a;
}

int myfunc(int a, int b)
{
    return a * b;
}

43. Определение системы и три примера систем

Система - множество взаимосвязанных и взаимодействующих объектов для решения одной или множества задач (достижения одной или множества целей).

Примеры систем:

  • Солнечная систем.
  • растение.
  • живой организм.
  • автомобиль.
  • компьютер.
  • разговорный язык.
  • математический язык.
  • нотные записи.
  • футбольный клуб,
  • Аврора

44. Перегрузка бинарных операторов

Vec operator+(const Vec& v2) const
{
    return Vec(x + v2.x, y + v2.y);
}
Vec operator+=(const Vec& v2)
{
    x += v2.x;
    y += v2.y;

    return *this;
}

Vec vec1(11, 12);
Vec vec2(5, -9);

Vec vec3 = vec1 + vec2;

45. При наследовании одного класса другим, когда вызываются конструкторы классов? Когда вызываются их деструкторы?

В C ++ конструкторы и деструкторы не наследуются. Однако они вызываются, когда дочерний класс инициализирует свой объект. Конструкторы вызываются один за другим иерархически, начиная с базового класса и заканчивая последним производным классом.

Деструкторы вызываются в обратном порядке.

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

Полиморфизм – переопределение наследниками методов выполняющих одну задачу, но по разному для разных классов иерархии.

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

47. Параметризированные конструкторы

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

class MyClass {
public:
  MyClass(int field); // конструктор c параметром
};

48. Программа – система

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

Любая программа (приложение), написанная с использованием ООП является системой. Приведем пример схемы приложения:

untitled

49. Перегрузка оператора индексации массивов [ ]

class Array
{
    int* arr;
    size_t size;
public:
    Array(size_t n);
    Array(const Array&);
    int& operator[](size_t);
    void operator()();
    ~Array();
};

Array::Array(size_t n): size(n)
{
    arr = new int[size];
}

Array::Array(const Array& arr2)
{
    arr = new int[arr2.size];

    for (size_t i = 0; i < arr2.size; i++)
        arr[i] = arr2.arr[i];
}

Array::~Array()
{
    delete[] arr;
}

int& Array::operator[](size_t pos)
{
    return arr[pos];
}

50. Присвоение объектов

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

Стандартное присваивание можно переопределить, перегрузив оператор присваивания для конкретного класса.

51. Приведение типов

В C++ для поддержки объектно-ориентированного программирования используется динамическая идентификация типа (RTTI - Run-Time Type Identification).

Оператор typeid возвращает ссылку на объект типа type_info, у которого определены операции ==, !=, а также у которого есть метод name, возвращающий указатель const char* на имя объекта.


В языке C++ существуют пять операторов приведения типов. Первый оператор является вполне традиционным и унаследован от языка С. Остальные четыре были добавлены впоследствии. К ним относятся операторы dynamic_cast, const_cast, reinterpret_cast и static_cast. Эти операторы позволяют полнее контролиро­вать процессы приведения типов.


Стандартное приведение типов:

void* arr = (float*)(malloc(size * sizeof(float)));

dynamic_cast

Осуществляет динамическое приведение типа с последующей про­веркой корректности приведения. Если приведение оказалось некорректным, оно не выполняется.

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

dynamic_cast<target_type>(expression);

Оператор dynamic_cast ****предназначен для приведения полиморфных типов.

До­пустим, даны два полиморфных класса B и D, причем класс D является производным от класса B. Тогда оператор dynamic_cast может привести указатель типа D* к ти­пу B*. Это возможно благодаря тому, что указатель на объект базового класса может ссылаться на объект производного класса. Однако обратное динамическое приведе­ние указателя типа D* к типу B* возможно лишь в том случае, если указатель дейст­вительно ссылается на объект класса D. Оператор dynamic_cast достигает цели, ес­ли указатель или ссылка, подлежавшие приведению, ссылаются на объект резуль­тирующего класса или объект класса, производного от результирующего. В противном случае приведение типов считается неудавшимся. В случае неудачи оператор dynamic_cast, примененный к указателям, возвращает нулевой указатель. Если оператор dynamic_cast применяется к ссылкам, в случае ошибки генерирует­ся исключительная ситуация bad_cast.

Примеры приведения типов:

class Base {
public:
    virtual void f() { std::cout << "Hello, World!\n"; }
};
class Derived: public Base {
public:
    void f() { std::cout << "Hello from Derived\n"; }
};

int main()
{
    Base b, *bp;
    Derived d, *dp;

    dp = dynamic_cast<Derived*>(&d); // то же, что и dp = &d

    bp = dynamic_cast<Base*>(&d); // bp = &d

    dp = dynamic_cast<Derived*>(bp);

    if (dp) std::cout << "Конвертация успешна" << std::endl;

    // указатель теперь ссылается на тип Base
    dp = dynamic_cast<Derived*>(&b);

    // 
    if (dp == nullptr) std::cout << "Конвертация неудачна" << std::endl;

    bp = &b;
}

**const_cast**

Оператор const_cast используется для явного замещения модификаторов const и/или volatile.

void sqrval(const int* val)
{
    int *p = const_cast<int*>(val);

    *p = *val * *val;
}

int main()
{
    int num = 10;

    std::cout << num << std::endl; // 10

    sqrval(&num);

    std::cout << num << std::endl; // 100

    return 0;
}

**reinterpret_cast**

Оператор преобразует один тип в совершенно другой.

Например, указатель в long

char num = 'B';
char* p_num = &num;

std::cout << reinterpret_cast<long>(p_num) << std::endl;

static_cast

Оператор заменяет обычное приведение:

int main()
{
    for (int i = 0; i < 10; i++)
        std::cout << static_cast<double>(i) / 3 << " ";
    return 0;
}

53. Перегрузка функций

Перегрузка функций — это использование одного имени для нескольких функций.

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

Конструкторы можно перегружать.

54. Перегрузка унарных операторов

class Vec
{
    double x, y;
public:
    Vec(): x(0), y(0) {}
    Vec(int _x, int _y): x(_x), y(_y) {}
    double get_x() const { return x; }
    double get_y() const { return y; }
    Vec& operator++()
    {
        x += 1;
        y += 1;

        return *this;
    }
    Vec& operator++(int)
    {
        x += 1;
        y += 1;

        return *this;
    }
};

постфиксный и префиксный инкременты перегружены одинаково.

operator++(int) - int здесь - затычка, чтобы отличить постфикс от префикса.

55. Структура. Назначение и синтаксис описания

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

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

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

struct Node
{
int data; //информационная часть узла: данные
Node *next; //поле связи: указатель на следующий узел
}; 

56. Операторы new и delete

Операция new предназначена для создания объекта.

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

MyClass* obj = new MyClass;

Операция delete уничтожает объект, созданный с помощью new.

delete obj;

57. Сигналы и обработчики

Подробнее

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

Есть сигналы, которые не могут быть пойманы программой, но есть следующий список сигналов, которые вы можете поймать в своей программе и можете принимать соответствующие действия на основе сигнала. Эти сигналы определены в файле заголовка C++ <csignal>.

  • SIGABRT — Аномальное завершение программы, например, вызов прерывания .
  • SIGFPE — Ошибочная арифметическая операция, такая как деление на ноль или операция, приводящая к переполнению.
  • SIGILL — Обнаружение незаконной инструкции.
  • SIGINT — Получение сигнала интерактивного внимания.
  • SIGSEGV —Недействительный доступ к хранилищу.
  • SIGTERM — Запрос завершения, отправленный в программу.

Библиотека управления сигналами <csignal> предоставляет signal() для обнаружения неожиданных событий:

void (*signal (int sig, void (*func)(int)))(int);
#include <iostream>
#include <csignal>
#include <unistd.h>

using namespace std;

void signalHandler(int signum)
{
    cout << "Interrupt signal (" << signum << ") received.\n";

    // cleanup and close up stuff here
    // terminate program

    exit(signum);
}

int main()
{
    // register signal SIGINT and signal handler
    signal(SIGINT, signalHandler);

    while (1)
    {
        cout << "Going to sleep...." << endl;
        sleep(1);
    }

    return 0;
}

Функция raise позволяет генерировать сигналы прерывания:

#include <iostream>
#include <csignal>
#include <unistd.h>

using namespace std;

void signalHandler(int signum)
{
    cout << "Interrupt signal (" << signum << ") received.\n";

    // cleanup and close up stuff here
    // terminate program

    exit(signum);
}

int main()
{
    int i = 0;
    // register signal SIGINT and signal handler
    signal(SIGINT, signalHandler);

    while (++i)
    {
        cout << "Going to sleep...." << endl;
        if (i == 3)
        {
            raise(SIGINT);
        }
        sleep(1);
    }

    return 0;
}

58. Форматированный ввод-вывод данных

Функции printf() и scanf() выполняют форматированный ввод-вывод на кон­соль, иначе говоря, они могут считывать и записывать данные в заданном формате.

Также для форматирования можно использовать библиотеку <iomanip>. В ней есть такие манипуляторы потока вывода как setw(), right, left и др.

59. Что такое родовой класс и какова его основная форма?

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

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

По соглашению родовой параметр обычно, использует имя G (от Generic ). Это неформальное правило. Если нужны еще родовые параметры, они будут названы H, I и т.д.

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

class STACK [G] feature

60. Что происходит, когда открытые члены базового класса наследуются как открытые? Что происходит, когда они наследуются как закрытые?

img.png

Открытое наследование

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

Закрытое наследование

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

61. Чисто виртуальные функции и абстрактные классы

Итак, если виртуальная функция не замещается в производном классе, вызывается ее версия из базового класса. Однако во многих случаях невозможно создать разум­ную версию виртуальной функции в базовом классе. Например, базовый класс может не обладать достаточным объемом информации для создания виртуальной функции. Кроме того, в некоторых ситуациях необходимо гарантировать, что виртуальная функция будет замещена во всех производных классах. Для этих ситуаций в языке C++ предусмотрены чисто виртуальные функции.

Чисто виртуальная функция (pure virtual function) — это виртуальная функция, не имеющая определения в базовом классе.

Чисто виртуальные функции должны переопределяться в каждом производном классе, в противном случае возникнет ошибка компиляции.

Синтаксис: virtual void make_sound() const = 0;

  • Пример
class Animal
{
    std::string species;
public:
    Animal(std::string);
    Animal(const char*);
    virtual void make_sound() const = 0;
    ~Animal();
};

class Wolf : public Animal
{
public:
    Wolf();
    void make_sound() const;
};

Animal::Animal(std::string _species): species(_species) {}

Animal::Animal (const char* _species): species(_species) {}

void Animal::make_sound() const
{
    std::cout << "Moo?.." << std::endl;
}

Animal::~Animal() {}

Wolf::Wolf(): Animal("wolf") {}

void Wolf::make_sound() const
{
    std::cout << "Woof! Woof!" << std::endl;
}

void trigger_make_sound(const Animal& animal)
{
    animal.make_sound();
}

Абстрактные классы

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

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

62. Что такое родовая функция и какова ее основная форма?

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

Родовая функция создается с помощью ключевого слова template. Обычное значение этого слова (шаблон в переводе на русский язык) точно отражает его использование в С++. С помощью template создается шаблон или каркас, который описывает, что должна делать эта функция, оставляя для компилятора задачу заполнения этого каркаса требуемыми деталями.

Общая форма определения родовой функции выглядит так:

template <typename тип >
тип-возврата имя-функции (список-параметров)
{
//тело функции
}

63. Что такое встраиваемая функция? В чем ее преимущества и недостатки?

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

Такие функции можно определить в классе, а можно объявить в классе как обычную функцию – член, а реализовать вне класса как функцию inline.

Тела функций встраиваются в код на месте вызова этой функции при выполнении препроцессорной обработки

inline void foo() {
  // тело функции
}

64. Чем действие дружественной оператор-функции отличается от действия оператор-функции — члена класса?

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

Функция-член класса — это функция, определенная внутри класса, а friend-функция — это функция, которая может обращаться к функциям-членам и переменным этого класса.

65. Что такое объект?

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

Объект - то, что может быть индивидуально описано и рассмотрено. Функциональные элементы системы (параметры и поведение которой определяется условием задачи), обладающие самостоятельным поведением (т.е. «умеющие» выполнять некоторые действия, зависящие от состояния и полученных сообщений) называют объектами.

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

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

66. Что происходит с защищенным членом класса, когда класс наследуется как открытый? Что происходит, когда он наследуется как закрытый?

img.png

67. Управление доступом при наследовании

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

68. Указатели и ссылки на объект

Чтобы использовать свойства и методы через указатель на объект, необходимо использовать -> вместо .

class MyClass {
public:
  void foo();
};

int main() {
  MyClass obj;
  MyClass obj_ptr = &obj;
  obj_ptr->foo();
}

В Visual C++ (CLR – Common Language Runtime) поддерживаются три типа указателей:

  • управляемые указатели (managed pointers);
  • неуправляемые указатели (unmanaged pointers);
  • неуправляемые указатели на функции (unmanaged function pointers).

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

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

Управляемые указатели – это указатели ссылочного типа. Эти указатели передаются для аргументов, методов, которые передаются по ссылке.

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

В таких указателях вместо символа ‘*’ применяется символ ‘^’. Для выделения.

Неуправляемые указатели (unmanaged pointer) – это традиционные указатели C/C++. Они являются указателями на объекты в неуправляемом объеме памяти, которая выделяется для выполнения программы. Неуправляемые указатели не являются совместимыми со спецификацией CLR.

Неуправляемые указатели на функции – это указатели на функции, которые можно обрабатывать таким же образом, как и неуправляемые указатели на объекты (данные).

Чтобы использовать свойства и методы через указатель на объект, необходимо использовать -> вместо.

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

Если класс MyClass1 является производным от класса MyClass2, то указатель типа MyClass1* может ссылаться на объекты типа MyClass2, если его привести к типу указателя MyClass2.

MyClass1* obj_ptr;
MyClass1 obj;
obj_ptr = &obj;
((MyClass2*)obj_ptr)->foo();

70. Управление доступом при наследовании

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

71. Указатель this

При вызове функции-члена ей неявно передается указатель на вызывающий объект — указатель this.

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

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

class ChildClass {
ParentClass* parent_ptr;
std::vector <ParentClass*> child_vector;
public:
  
};

MyClass::MyClass(ParentClass* parent_ptr) {
  parent_ptr->child_vector.push_back(this);
}

72. Шаблон класса

Шаблон класса (обобщенный класс) позволяет определять класс для многих типов данных.

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

template <typename T>
class Stack
{
    T* stack;
    size_t size;
public:
    Stack(size_t _size);
    bool pop(T& into);
    bool push(const T& val);
    size_t max_size() const;
    size_t cur_size() const;
};

template <typename T>
Stack<T>::Stack(size_t _size): size(_size) {}

Применение стандартных типов в обобщенных классах

template <typename T, int size>
class Stack
{
    T stack[size];
    size_t max_size;
public:
    Stack();
    bool pop(T& into);
    bool push(const T& val);
    size_t get_max_size() const;
    size_t get_cur_size() const;
};

template <typename T, int size>
Stack<T, size>::Stack()
{
    max_size = size;
}

Применение аргументов по умолчанию в шаблонных классах

template <typename T = int>
class myclass { /* ... */ }

Явная специализация класса

template <typename T> class myclass {}

// Явная специализация класса
template <> class myclass<int> 
{

} 

73. Шаблон функции

Шаблон функции (обобщенная функция) позволяет определять функцию для многих типов данных.

template <typename T>
void print_arg(T arg)
{
    std::cout << arg << std::endl;
}

Явная перегрузка шаблонной функции

void print_arg(double arg)
{
    std::cout << "Printing 'double' type value": arg << std::endl;
}

Ограничения на обобщенные функции

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

74. Почему следующие две перегруженные функции внутренне неоднозначны?

int f ( int a ); //Внутри данной функции создается 
                //локальная переменная и изменяется она локально, соответственно
int f ( int & a ); //Функция получает на вход ссылку на переменную, и, соответственно,
                //изменяется значение переменной по ссылке (а не локально, как в первом случае)

75. Почему следующая функция может не компилироваться как встраиваемая?

Не хватает ключевого слова inline

/*inline*/ void fl ( ) {
    int i;
    for( i = 0; i < 10; i++ ) 
        cout << i;
}

76. Что неправильно в данном фрагменте?

int main ( ) {
 . . . .
 throw 12.23;
}
int main ( ) {
    try {
        throw 12.23;
    }
    catch (double i) {
        ///
    }
    return 0;
}

77. Что неправильно в следующем прототипе функции?

char * f ( char * p, int x = 0,  char * q );
//Аргумент по умолчанию должен быть указан в конце списка параметров
char * f ( char * p, char * q, int x = 0 );

78. Что неправильно в конструкторе, показанном в следующем фрагменте?

class sample 
    double a, b, с;
public:
    double sample ( );
}
class sample 
    double a, b, с;
public:
    sample ( );
    //Конструктор объявляется без типа а точнее его тип это класс к которому он принадлежит
}

79 Правилен ли следующий фрагмент?

union /*name*/{
    float f;
    unsigned int bits;
}; //должно быть название объединения и ';' после

80. Какой будет результат после отработки данной программы?

#include <iostream>
#include <list>
using namespace std;
int main() {
    list < char > lst;
    list < char > ::iterator it_lst;
    int i;
    for (i = 0; i < 10; i++) lst.push_back('A' + i);
    cout << "Size = " << lst.size() << endl;
    cout << "lst: ";
    while (!lst.empty()) {
        it_lst = lst.end();
        it_lst--;
        cout << *it_lst;
        lst.pop_back();
    }
    return 0;
}

Вывод программы:

Size = 10

lst: JIHGFEDCBA

81. Какой будет результат после отработки данной программы?

#include <iostream>
using namespace std;
class static_func_demo {
    static int i;
public:
    static void init(int x) { i = x; }
    void show() { cout << i; }
};
int static_func_demo::i;
int main()
{
    static_func_demo::init(100);
    static_func_demo x;
    x.show();
    return 0;
}

Вывод: 100

82. Какой будет результат после отработки данной программы?

#include <iostream>
using namespace std;
class samp {
    int i;
public:
    samp(int n) { i = n; }
    void set_i(int n) { i = n; }
    int get_i() { return i; }
};
void sqr_it(samp ob) {
    ob.set_i(ob.get_i() * ob.get_i());
    cout << ob.get_i() << "\n";
}
int main() {
    samp a(10);
    sqr_it(a);
    cout << a.get_i();
    return 0;
}

Вывод:

100

10

83. Исследуйте следующую конструкцию

#include <iostream>
using namespace std;
class cl_base {
    int a, b;
public:
    int c;
    void setab ( int i, int j ) { a = i; b = j; }
    void getab ( int & i, int & j ) { i = a; j = b; }
};
class derived_1 : pablic cl_base { };
class derived_2 : private cl_base { };
int main ( ) {
    derived_1 ob_1;
    derived_2 ob_2;
    int i, j;
    // . . . . .
    return 0;
}

Какая из следующих инструкций правильна внутри функции main ( )?

A. ob_1.getab ( i, j ); правильно

B. оb_2.getab ( i, j );

C. ob_1.c = 10; правильно

D. ob_2.c = 10;

84. Объясните, что в следующей программе неправильно, и исправьте ее

#include <iostream>
using namespace std;
class cl_1 {
    int* p;
public:
    cl_1(int i);
    ~cl_1() { delete p; }
    friend int getval(cl_1 ob);
};
cl_1::cl_1(int i) {
    p = new int;
    if (!p) {
        cout << "Error 1\n";
        exit(1);
    }
    *p = i;
}
int getval(cl_1 ob) { return *ob.p; }
int main() {
    cl_1 a(1), b(2);
    cout << getval(a) << " " << getval(b);
    cout << "\n";
    cout << getval(a) << " " << getval(b);
    return 0;
}

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

Для исправления достаточно передать ссылку на объект в функцию:

int getval(cl_1& ob) { return *ob.p; }

85. Какой будет результат после отработки данной программы?

#include <iostream>
using namespace std;
class A {
public: 
    A ( ) { cout << "Constructor A\n"; }
    ~A ( ) { cout << "Destructor A\n"; }
};
class B {
public:
    B ( ) { cout << "Constructor B\n"; }
    ~B ( ) { cout << "Destructor B\n"; }
};
class C : public A, public B {
public:
    C ( ) { cout << "Constructor C\n"; }
    ~C ( ) { cout << "Destructor C\n"; }
};
int main ( ) {
    C ob;
    return 0;
}

Вывод:

Constructor A

Constructor B

Constructor C

Destructor C

Destructor B

Destructor A

86. В следующей программе имеется ошибка. Исправьте ее с помощью оператора const_cast

#include <iostream>
using namespace std;
void f ( const double & i )
{
    i = 100; // const_cast<double&> (i) = 100;
}
int main ( )
{
    double x = 98.6;
    cout << x << endl;
    f ( x );
    cout << x << endl;
    return 0;
}

87. Ниже приведены две перегруженные функции. Покажите, как получить адрес каждой из них

#include <iostream>
using namespace std;
int dif ( int a, int b ) { return a — b; }
float dif ( float a, float b ) { return a — b; }
int main ( ) {
    /*
    int (*p1) (int a, int b) = dif;
    float (*p2) (float a, float b) = dif;
    */
    return 0;
}

88. Какой будет результат после отработки данной программы?

#include <iostream>
using namespace std;
union bits {
    bits ( short n );
    void show_bits ( ) ;
    short d;
    unsigned char c [ sizeof ( short ) ];
};
bits :: bits ( short n ) { d = n; }
void bits :: show_bits ( ) {
    int i, j;
    for ( j = sizeof ( short ) - 1; j >= 0; j -- ) {
        cout << "Byte " << j << ":";
        for( i = 128; i; i >>= 1 )
            if ( i & c [ j ] ) cout << "1";
            else cout << "0";
            cout << "\n";
    }
}
int main ( ) {
    bits ob ( 5 );
    ob.show_bits ( );
    return 0;
}

Вывод программы:

Byte 1:00000000

Byte 0:00000101

89. Какой будет результат после отработки данной программы?

#include <iostream>
using namespace std;
template < class T >
T & inc_value ( T & val ) {
    ++val;
    return val;
}
int main ( )
{
    int x = 64;
    char c = 64;
    x = ( int ) inc_value < int > ( x );
    cout << x << endl;
    c = ( char ) inc_value < char > ( c );
    cout << c << endl;
    printf ( "%02X", c );
    return 0;
}

Вывод:

65

A

41

90. Какой будет результат после отработки данной программы?

#include <iostream>
using namespace std;
int main ( ) {
    union {
        unsigned char bytes [ 4 ];
        int value;
    };
    int i;
    value = 128;
    for ( i = 3; i >= 0; i -- )
        cout << ( int ) bytes [ i ] << " ";
    return 0;
}

Вывод программы: 0 0 0 128

91. Какой будет результат после отработки данной программы?

#include <iostream>
using namespace std;
class samp {
    int i;
public:
    samp ( int n ) { i = n; }
    void set_i ( int n ) { i = n; }
    int get_i ( ) { return i; }
};
    void sqr_it ( samp * ob ) {
    ob -> set_i ( ob -> get_i ( ) * ob -> get_i ( ) );
    cout << ob -> get_i ( ) << "\n";
}
int main ( ) {
    samp a ( 10 );
    sqr_it ( & a );
    cout << a.get_i ( );
    return 0;
}

Вывод программы:

100

100

92. Какой будет результат после отработки данной программы ?

#include <iostream>
using namespace std;
template < class T1 >
void PrintArray(const T1* array, const int count)
{
    for (int i = 0; i < count; i++)
        cout << array[i] << " ";
    cout << endl;
}
int main()
{
    const int aCount = 5;
    const int bCount = 7;
    const int cCount = 6;
    int a[aCount] = { 1,2,3,4,5 };
    double b[bCount] = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7 };
    char c[cCount] = "HELLO";
    cout << "Array a:" << endl;
    PrintArray(a, aCount);
    cout << "Array b:" << endl;
    PrintArray(b, bCount);
    cout << "Array c:" << endl;
    PrintArray(c, cCount);
    return 0;
}

Вывод программы:

Array a:

1 2 3 4 5

Array b:

1.1 2.2 3.3 4.4 5.5 6.6 7.7

Array c:

H E L L O

93. Дана следующая программа, переделайте все соответствующие обращения к членам класса так, чтобы в них явно присутствовал указатель this

#include <iostream>
using namespace std;
class cl_1 {
    int a, b;
public:
    cl_1(int n, int m) { a = n; b = m; }
    //cl_1(int a, int b) { this->a = a; this->b = b; }
    int add() { return a + b; }
    //int add() { return this->a + this->b; }
    void show();
};
void cl_1::show() {
    int t;
    t = add();
    cout << t << "\n";
    //cout << this->add() << "\n";
}
int main() {
    cl_1 ob(10, 14);
    ob.show();
    return 0;
}

94.Какой будет результат после отработки данной программы ?

#include <iostream>
using namespace std;
class base {
public:
    base() { cout << "Constructor base\n"; }
    ~base() { cout << "Destructor base\n"; }
};
class derived : public base {
public:
    derived() { cout << "Constructor derived\n"; }
    ~derived() { cout << "Destructor derived\n"; }
};
int main() {
    derived ob;
    return 0;
}

Вывод программы:

Constructor base

Constructor derived

Destructor derived

Destructor base

95. Что неправильно в следующей программе ?

#include <iostream>
using namespace std;
void triple(double& num);
int main() {
    double d = 7.0;
    triple(&d);
    //triple(d); необходимо передавать не по ссылке
    cout << d;
    return 0;
}

96. Какой будет результат после отработки данной программы ?

#include <iostream>
using namespace std;
union swapbytes {
    unsigned char c[2];
    unsigned short i;
public:
    swapbytes(unsigned short x);
    void swp();
};
swapbytes::swapbytes(unsigned short x) { i = x; }
void swapbytes::swp() {
    unsigned char temp;
    temp = с[0];
    c[0] = c[1];
    c[1] = temp;
}
int main() {
    swapbytes ob(1);
    ob.swp();
    cout << ob.i;
    return 0;
}

Вывод программы:

256

97. Какой будет результат после отработки данной программы?

#include <iostream>
using namespace std;
int rotate(int i) {
    int x;
    if (i & 0x80000000) x = 1;
    else x = 0;
    i = i << 1;
    i += x;
    return i;
}
int main() {
    int a;
    a = 0x80000001;
    cout << rotate(a);
    return 0;
}

Вывод программы:

3

98. Какой будет результат после отработки данной программы ?

#include <iostream>
using namespace std;
class cl_1 {
public:
    static int i;
    void seti(int n) { i = n; }
    int geti() { return i; }
};
int cl_1::i;
int main() {
    cl_1 ol, o2;
    cl_1::i = 100;
    cout << "ol.i: " << ol.geti() << '\n';
    cout << "o2.i: " << o2.geti() << '\n';
    return 0;
}

Вывод программы:

ol.i: 100

o2.i: 100

99. Какой будет результат после отработки данной программы ?

#include <iostream>
using namespace std;
int rotate(int i) {
    int x;
    if (i & 0x80000000) x = 1;
    else x = 0;
    i = i << 1;
    i += x;
    return i;
}
int main() {
    int a;
    a = 0x80000000;
    cout << rotate(a);
    return 0;
}

Вывод программы:

1

100. Какой будет результат после отработки данной программы ?

#include <iostream>
using namespace std;
union swapbytes {
    unsigned char c[2];
    unsigned short i;
public:
    swapbytes(unsigned short x);
    void swp();
};
swapbytes::swapbytes(unsigned short x) { i = x; }
void swapbytes::swp() {
    unsigned char temp;
    temp = c[0];
    c[0] = c[1];
    c[1] = temp;
}
int main() {
    swapbytes ob(256);
    ob.swp();
    cout << ob.i;
    return 0;
}

Вывод программы:

1

101. Какой будет результат после отработки данной программы ?

#include <iostream>
using namespace std;
int main()
{
    unsigned char c = 0x1A;
    c <<= 4; // умножить на 16
    c >>= 4; // разделить на 16
    printf("c = %02X", c);
    return 0;
}

Вывод программы:

c = 0A

102. Какой будет результат после отработки данной программы ?

#include <iostream>
#include <typeinfo>
using namespace std;
class BaseClass {
    virtual void f() { }
};
class Derivedl : public BaseClass { };
class Derived2 : public BaseClass { };
int main() {
    int i;
    BaseClass* p, baseob;
    Derivedl ob1;
    Derived2 ob2;
    cout << "Type - " << typeid (i).name() << endl;
    p = &baseob;
    cout << "Type - " << typeid (*p).name() << endl;
    p = &ob1;
    cout << "Type - " << typeid (*p).name() << endl;
    p = &ob2;
    cout << "Type - " << typeid (*p).name() << endl;
    return 0;
}

Вывод программы:

Type - int

Type - class BaseClass

Type - class Derivedl

Type - class Derived2

103. Какой будет результат после отработки данной программы ?

#include <iostream>
using namespace std;
class Base {
public:
    virtual void f() { }
};
class Derived : public Base {
public:
    void derived_only() {
        cout << "It's object of class Derived\n";
    }
};
int main() {
    Base* bp, b_ob;
    Derived* dp, d_ob;
    bp = &b_ob;
    dp = dynamic_cast <Derived*> (bp);
    if (dp) dp->derived_only();
    else cout << "Error 1\n";
    bp = &d_ob;
    dp = dynamic_cast <Derived*> (bp);
    if (dp) dp->derived_only();
    else cout << "Error 2\n";
    return 0;
}

Вывод программы:

Error 1

It's object of class Derived

104. Что неправильно в следующем фрагменте ?

#include <iostream>
using namespace std;
class cl1 {
    int i, j;
public:
    cl1(int a, int b) { i = a; j = b; }
};
class cl2 {
//class cl2 :public cl1 {
    int i, j;
public:
    cl2(int a, int b) { i = a; j = b; }
    //cl2(int a, int b) :cl1(a, b) {}
};
int main() {
    cll x(10, 20);
    cl2 y(0, 0);
    x = y;
    . . . . .
}

About

Вопросы к экзамену по ООП

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published