[ ] FAQs
OOP -
OOD -
TDD - test-driven development. Сначала тест, потом код.
Sort algorithms -
Polymorphism
Answer: Один интерфейс, множество реализаций (методов) Выполнение каждого конкретного действия будет определяться типом данных Тип данных, который используется при вызове функции, определяет, какая конкретная версия функции выполняется
Возможность объектов с одинаковой спецификацией иметь различную реализацию.
class Figure { void Draw() const; };
class Square : public Figure { void Draw() const; };
class Circle : public Figure { void Draw() const; };
Т.е. есть базовый класс фигура, и от него унаследованы два класса квадрат и круг, имеющие собственную реализацию метода Draw().
Template specialization
Answer:
// вот пример общей специализации
template<typename T>
struct is_integer {
enum { value = 0; }
};
// а вот частичная специализация
template<>
struct is_integer<int> {
enum { value = 1; }
};
Template instantiation
Answer: Для каждого набора параметров компилятор генерирует новый экземпляр объекта. Процесс создания нового экземпляра - инстанцирование шаблона.
Placement new
Answer: Существует особая форма оператора new, называемая Placement new. Данный оператор не выделяет память, а получает своим аргументом адрес на уже выделенную каким-либо образом память (например, на стеке или через malloc). Происходит размещение (инициализация) объекта путем вызова конструктора, и объект создается в памяти по указанному адресу. Часто такой метод применяют, когда у класса нет конструктора по умолчанию и при этом нужно создать массив объектов. Пример вызова выглядит следующим образом:
class A {
public:
A(int x) {}
~A() {}
};
const int n = 50;
A *placementMemory = static_cast<A *>(operator new[] (n * sizeof(A)));
for (int i = 0; i < n; i ++) {
// здесь память для объекта не выделяется, но инициализируется
new (placementMemory + i) A( rand() );
}
// !!деинициализация памяти
for (int i = 0; i < n; i ++) {
placementMemory[i].~A();
}
operator delete[] (placementMemory);
Поскольку при выделении памяти тип создаваемого объекта(ов) не был указан, компилятор не будет вызывать деструктор для каждого объекта массива, поэтому это нужно сделать вручную, перед освобождением блока памяти.
Дано целое число, надо без циклов заменить в двоичном представлении этого числа самый правый ноль на единицу.
Answer: i |= (i + 1)
Что такое полиморфизм, наследование, инкапсуляция? Что такое динамический полиморфизм и статический? Чем они отличаются?
Answer: Возможность работать с объектами разных классов одинаковым образом. Поддержка различного поведения родственных классов, предоставляемого через единый интерфейс базового класса. Есть статический – на этапе компиляции. Шаблоны, перегрузка функций и операторов. Есть динамический – виртуальные функции. Точное значение операции определяется объектом для которого она вызывается.
Инкапсуляция - сокрытие деталей реализации.
Какие функции будут добавлены в приведенном ниже классе?
class CSomeClass{};
Answer: Конструктор по-умолчанию, конструктор копирования, оператор присваивания, деструктор.
Какая здесь ошибка?
class CSomeClass {
public:
int *ptr;
CSomeClass() {
ptr = new int;
*ptr = 1;
}
~CSomeClass() {
delete ptr;
}
};
void f(CSomeClass ob) {
std::cout << *(ob.ptr) << std::endl;
}
void main()
{
CSomeClass ob;
f(ob);
std::cout << *(ob.ptr) << std::endl;
}
Answer: Неопределен конструктор копирования, поэтому при передаче объекта в функцию создается его копия. Т.е. у нас появляется 2 объекта, у которых ptr ссылается на одну область памяти. Потом для созданной копии вызывается деструктор, который удаляет общую область памяти. В итоге, когда мы пытаемся вывести значения буфера, то получаем access violation.
Какая здесь ошибка?
class CAssignableClass {
int x;
public:
CAssignableClass(int thex){
x = thex;
}
void operator = (const CAssignableClass &ob) {
x = ob.x;
}
};
int main()
{
CAssignableClass ob1(1), ob2(2), ob3(3);
ob1 = ob2 = ob3;
return 0;
}
Answer: По уму, перегруженные операторы присваивания должны возвращать *this, поскольку в данном примере они ничего не возвращают, то во время двойного присваивания возникает ошибка компиляции. Что-то вроде «can’t assign void».
Какая здесь ошибка?
class CBaseClass{
CBaseClass(){}
public:
static CBaseClass *Construct() {
return new CBaseClass();
}
};
class CDerivedClass : public CBaseClass {
public:
CDerivedClass() {}
};
int main()
{
CDerivedClass ob;
return 0;
}
Answer: Конструктор по-умолчанию в базовом классе объявлен закрытым. Поэтому когда мы попытаемся создать объект производного класса, то получим ошибку компиляции – компилятор укажет, что для создания объекта ob нужен вызов конструктора базового класса, но он объявлен закрытым, потому объекты производного класса создать нельзя.
Какая здесь ошибка?
#include <iostream>
class A {
protected:
int i;
};
class B: public A {};
class C: public A {};
class D: public B, C {
public:
D () {
B::i = 1;
std::cout << "B::i = " << B::i << std::endl;
C::i = 2;
std::cout << "C::i = " << C::i << std::endl;
// i = 3; // error: reference to 'i' is ambiguous
}
};
int main()
{
D d;
return 0;
}
Answer: stdout: B::i = 1 C::i = 2
Ошибка возникнет во время присваивания i = 3. Т.к. компилятор не будет знать какому инстансу этой переменной присваивать, B::i или C::i.
Зачем в STL нужны итераторы?
Answer: Для того чтобы был унифицированный доступ к элементам последовательности. Собственно если стандарт открыть, то там оперируют именно термином «последовательность», без указания деталей реализации этой последовательности. Это может быть вектор, стэк или очередь. Итераторы позволяют от этого всего абстрагироваться.
Чем отличается выделение памяти с помощью new и malloc?
Answer: При выделении памяти с помощью new для объекта, будет вызван конструктор этого объекта. + new является оператором и его можно перегружать для каждого класса.
Что делает следующая программа
#include <algorithm>
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(2);
v.push_back(3);
v.erase( std::remove(v.begin(), v.end(), 2), v.end() );
return 0;
}
Answer: Происходит удаление все элементов равных «2». Не очень быстро, зато элегантно.
Какими свойствами должен обладать объект, чтобы его можно было хранить в STL контейнере?
Answer: Должен обладать корректными конструктором копирования и оператором присваивания.
Что вернет функция
int f (int i, int j, int k) {
return k;
}
если её вызвать так:
int i(1);
std::cout << f(++ i, ++ i, ++ i) << std::cout;
Что такое undefined behaviour? Приведите примеры, когда программа начинает себя так вести.
Answer: В данном случае как раз undefined behaviour и поэтому сказать, что именно вернет функция, нельзя. Связано это с тем что порядок вычисления параметров функции неопределен стандартом. Ну а что касается примера – файл с C++ кодом должен заканчиваться символом переноса строки, иначе это ub.
Что неправильно в данном коде:
template <int n>
class t {};
// ...
for (int i(0); i < 10; i ++) {
t<i> ob;
}
Answer: Параметры инстанциации шаблона должны быть известны компилятору на этапе компиляции. А в данном случае получается, что они неизвестны. Поэтому и возникнет ошибка в строчке t.
Сколько байт в памяти займет приведенный ниже класс?
class CSomeClass{
public:
int *ptr;
virtual void f() {}
};
Answer: Во-первых, у нас есть виртуальная функция, поэтому в объекте всегда будет присутствовать указатель таблицу виртуальных функций, и занимать, как правило, 4 байта. Во-вторых, есть указатель на целое число. Он, в зависимости от платформы, может занимать 2, 4 или 8 байт 4 (то же самое относится и к указателю на таблицу виртуальных методов).
Что будет выведено на экран?
void f (int i) {
std::cout << i << std::endl;
}
int main()
{
f ( (0, 1, 2) );
_getch();
return 0;
}
Answer: Здесь обыгрывается тот факт, что в C++ запятая это оператор действующий слева направо. Поэтому в функцию f будет передано значение «2».
Сколько места в памяти занимает объект класса? Как это узнать?
Answer: Столько, сколько все его поля, кроме статических, плюс размер указателя – информация о типе (1 char на gcc 32 bit).
Каков размер «пустого» объекта?
Answer: В типичной реализации – размер указателя. Это информация о типе.
Какие операции нельзя перегружать? Как вы думаете, почему?
Answer: sizeof, ., .*, typeid ?:
потому что их операнд – это имя типа.
Можно ли перегружать операции для встроенных типов данных?
Answer: Нет, потому что тип – это набор данных и операций над ними. Изменяя семантику типа, мы меняем само определение типа.
Что определяет класс? Чем обличается класс от объекта?
Answer: Класс определяет пользовательский тип: описание данных и операций над ними. Объект – это конкретный экземпляр класса со своим состоянием.
Можно ли при перегрузке изменить приоритет операции?
Answer: Нет.
Можно ли определить новую операцию?
Answer: Нет. Надо пользоваться функциями.
Какой результат должны возвращать операции с присваиванием?
Answer: Ссылку
Как различаются перегруженная префиксная и постфиксная операции инкремента и декремента?
Answer: Постфикс имеет фиктивный параметр.
Какие операции не рекомендуется перегружать как методы класса? Почему?
Answer: Те, которым не нужен доступ к внутренним переменным класса. Чтобы не нарушать инкапсуляции.
Какие операции разрешается перегружать только как методы класса?
Answer: Те, для которых нужна гарантия, что их первый операнд lvalue. = [] ->
Дайте определение дружественной функции. Как объявляется дружественная функция? А как определяется?
Answer: Функция имеет доступ к закрытой части класса. Не находится в области видимости класса. Не должна вызываться для объекта класса.
Дайте определение конструктора. Каково назначение конструктора? Перечислите отличия конструктора от метода.
Answer: Специальный метод для инициализации объекта. Не имеет возвращаемого значения. Не может быть статическим, виртуальным. Внутри не работает механизм виртуальных функций, потому что ещё не инициализирована таблица виртуальных функций.
Сколько конструкторов может быть в классе? Допускается ли перегрузка конструкторов? Какие виды конструкторов создаются по умолчанию?
Answer: Сколько угодно. Да. default ctor, copy ctor.
Может ли конструктор быть приватным? Какие последствия влечет за собой объявление конструктора приватным?
Answer: Да. Невозможность конструирования объекта класса.
Приведите несколько случаев, когда конструктор вызывается неявно.
Answer: Копирование, конструирование внутренних объектов, передача по значению, приведение типа.
Как проинициализировать динамическую переменную?
Answer: в списке инициализаторов new placement new new(nothrow)
Как объявить константу в классе? Можно ли объявить дробную константу?
Answer: const int x; const double x;
Каким образом разрешается инициализировать константные поля в классе?
Answer: статические интегральные прямо на месте, остальные в инициализаторе или вне класса, или в конструкторе
Какие конструкции С++ разрешается использовать в списке инициализации качестве инициализирующих выражений?
Answer: rvalue, инициализированные lvalue
Какой вид конструктора фактически является конструктором преобразования типов?
Answer: Конструктор не explicit
Для чего нужны функции преобразования? Как объявить такую функцию в классе?
Answer: Для корректного приведения неродственных типов между собой. Как операторы преобразования. static_cast – иерархическое преобразование с проверкой на этапе компиляции reinterpret_cast – несвязанное преобразование с проверкой на этапе компиляции dynamic_cast – проверяется на этапе выполнения RTTI. const_cast – аннулирует const и volatile operator int() const {return v;}
Как запретить неявное преобразование типа, выполняемое конструктором инициализации?
Answer: explicit
Какие проблемы могут возникнуть при определении функций преобразования?
Answer: Если определены операторы и преобразования, то может возникнуть конфликт между неявным преобразованием и пользовательскими операторами.
Для чего служит ключевое слово explicit?
Answer: Это модификатор для запрещения неявного преобразования типов объектов аргументов конструктора.
Влияет ли наличие целочисленных констант-полей на размер класса?
Answer: Если не static, то да.
Разрешается ли объявлять массив в качестве поля класса. Как присвоить элементам массива начальные значения?
Answer: Да. В классе – только в конструкторе. Если статический константый, то вне пределов класса.
Сколько операндов имеет операция индексирования []? Какой вид результата должна возвращать эта операция?
Answer: Один. Любой. Чаще лучше ссылку.
Для чего нужны статические поля в классе? Как они определяются?
Answer: Для хранения состояния класса.
static int i;
Как объявить в классе и проинициализировать статический константный массив?
Answer: class A { static const int I[3]; };
A::I[3] = {1, 2};
Что такое выравнивание и от чего оно зависит? Влияет ли выравнивание на размер класса?
Answer: Расположение данных в памяти поблочно, так как процессор читает машинными словами. Зависит от реализации компилятора, ОЗУ и процессора. Влияет.
Дайте определение контейнера.
Answer: Контейнер – это объект, хранящий другие объекты.
Какие виды встроенных контейнеров в С++ вы знаете? На основе чего они реализованы
Answer: Фундаментальные последовательности vector list deque Адаптеры stack queue priority_queue Ассоциативные контейнеры и множества map multimap set multiset Почти контейнеры string valarray bitset vector
Какие виды доступа к элементам контейнера вам известны?
Answer: Разыменовывание итератора и оператор индексирования, также специальные функции.
Чем отличается прямой доступ от ассоциативного?
Answer: Индексацией: целыми числами или значениями
Перечислите операции, которые обычно реализуются для последовательного доступа к элементам контейнера.
Answer: begin(), end(), инкремент итератора, разыменование.
Дайте определение итератора.
Answer: Итератор это абстрактная модель данных, как последовательности объектов. Объект, который ведёт себя, как итератор – есть итератор. Ключевые концепции: инкремент, равенство и разыменование.
Можно ли реализовать последовательный доступ без итератора? В чем преимущества реализации последовательного доступа с помощью итератора?
Answer: Да. Единообразная работа с различными сущностями.
Что играет роль итератора для массивов С++?
Answer: Имя массива – указатель на начало и арифметика указателей.
Что такое деструктор? Может ли деструктор иметь параметры?
Answer: Специальная функция, отвечающая за уничтожение объекта. Нет.
Почему для классов-контейнеров деструктор надо писать явным образом?
Answer: Потому что управление переменной длиной конейнера требует динамического выделения памяти, которую, соответственно, надо уничтожать.
Допускается ли перегрузка деструкторов?
Answer: Нет, потому что он не имеет параметров.
Что такое «глубокое копирование» и когда в нем возникает необходимость?
Answer: Копирование значений полей класса, даже если они заданы указателями. При сериализации.
Какое копирование осуществляет стандартный конструктор копирования?
Answer: Вызывает конструкторы копирования баз и членов. Неглубокое копирование, если по умолчанию.
Чем отличается копирование от присваивания?
Answer: Это разные операции Копирование – работает конструктор копирования, создаётся новый объект. Присваивание – поля одного объекта заполняются значениями другого. Нового объекта не создаётся.
Объясните, почему в операции присваивания требуется проверка присваивания самому себе?
Answer: Не требуется, но рекомендуется. Для больших объектов весьма существенный выигрыш по времени.
Можно ли в качестве операции индексирования использовать операцию вызова функции ()? В чем ее преимущества перед операцией []?
Answer: Можно сделать это макросами. Можно сделать перегрузку оператора () Вызов с несколькими аргументами.
Почему необходимо писать два определения операции индексирования? Чем они отличаются?
Answer: Модификатором const. Нужно, чтобы была возможность изменять содержимое неконстантного контейнера и иметь доступ к константному.
Дайте определение вложенного класса.
Answer: Область видимости ограничена классом, в котором он объявлен.
Можно ли класс-итератор реализовать как внешний класс? А как вложенный? В чем отличия этих методов реализации?
Answer: Да. Да. Во втором случае объект знает о типе итератора заранее.
Может ли объемлющий класс иметь неограниченный доступ к элементам вложенного класса? А вложенный класс — к элементам объемлющего?
Answer: Нет. Да.
Ограничена ли глубина вложенности классов?
Answer: Нет.
Можно ли определить вложенный класс внешним образом? Зачем это может понадобиться?
Answer: Да. Если вложенный класс нуждается в определении класса, в который он вложен. Или при раздельной трансляции.
Каким образом вложенный класс может использовать методы объемлющего класса? А объемлющий — методы вложенного?
Просто, вызывая их, если public.
Answer: Или сделав дружественный класс.
Что такое «запредельный» элемент, какую роль он играет в контейнерах?
Answer: Элемент, следующий за последним. Условие окончания перебора элементов.
Объясните, по каким причинам трудно написать универсальный контейнер, элементы которого могут иметь произвольный тип.
Answer: Из-за строгой типизации.
Назовите ключевые слова С++, которые используются для обработки исключений.
Answer: try, catch, throw
Исключение — это:
Answer:
- событие;
- ситуация;
- объект;
- ошибка в программе;
- прерывание;
событие и объект, смотря с какой точки зрения.
Каким образом исключение генерируется?
Answer: Раскрутка стека. Поиск секции-обработчика.
Каковы функции контролируемого блока?
Answer: Отловить исключение, сгенерированное внутри него.
Что обозначается ключевым словом catch?
Answer:
- контролируемый блок;
- блок обработки исключения;
- секция-ловушка;
- генератор исключения;
- обработчик прерывания;
2, 3, 5
Какого типа может быть исключение?
Answer: Любого. Это класс.
Сколько параметров разрешается писать в заголовке секции-ловушки?
Answer: Один.
Какими способами разрешается передавать исключение в блок обработки?
Answer: По значению, по ссылке или по указателю.
Объясните, каким образом преодолеть ограничение на передачу единственного параметра в блок обработки.
Answer: Завести класс, структуру или использовать несколько блоков обработки.
Почему нельзя выполнять преобразования типов исключений при передаче в секцию-ловушку?
Answer: Потому что может сработать не та ловушка.
Напишите конструкцию, которая позволяет перехватить любое исключение.
Answer: catch (...) {}
Могут ли контролируемые блоки быть вложенными?
Answer: Да.
Зачем нужен «контролируемый блок-функция» и чем он отличается от обычного контролируемого блока?
Answer: Нужен для перехвата исключения в списке инициализации конструктора. Используется для преобразования типа исключения: ловится одно, кидается другое.
class X {
vector v;
public:
explicit X(int);
};
X::X(int s)
try
:v(s)
{
//...
} catch (bad_alloc) {
//...
}
Перечислите возможные способы выхода из блока обработки.
Answer: Нормальный выход, генерация исключения, передача сгенерированного исключения.
Каким образом исключение «передать дальше»?
Answer:
throw
Сколько секций-ловушек должно быть задано в контролируемом блоке?
Answer: Сколько угодно. Главное – правильная последовательность.
Что такое «спецификация исключений»?
Answer: Это часть объявления функции, которая указывает набор генерируемых ею исключений и производных от них.
void fu(int a) throw(x2, x3);
Что происходит, если функция нарушает спецификацию исключений?
Answer: вызовется std::unexpected() и программа завершит работу terminate().
Учитывается ли спецификация исключений при перегрузке функций?
Answer: Спецификация должна быть не менее строгой. Потомок может генерировать производные исключения.
Что такое «иерархия исключений»?
Answer: То же, что иерархия классов.
Существуют ли стандартные исключения? Назовите два-три типа стандартных исключений.
Answer: Да. std::exception
Поясните «взаимоотношение» исключений и деструкторов.
Answer: Объект уничтожается в конце его области видимости, если сгенерировалось исключение. Деструктор отрабатывает.
Объясните, зачем может понадобиться подмена стандартных функций завершения.
Answer: Для собственной обработки ситуаций (обычно для записи в лог).
Какие виды нестандартных исключений вы знаете?
Answer: Нестандартные – потому что не успели включить в стандарт?
В чем отличие механизма структурной обработки исключений Windows от стандартного механизма?
Answer: этот механизм называется SEH. предоставляет две схемы обработки.
__try {} __except{} и __try{} __finally{}
в SEH существует только единственный обработчик. Описано у Рихтера.
Какие две роли выполняет наследование?
Answer: Формирует иерархию и поощряет повторное использование кода.
Какие виды наследования возможны в С++?
Answer: Обычное, множественное, виртуальное.
Чем отличается модификатор доступа protected от модификаторов private и public?
Answer: Видимостью наследующему классу.
Чем открытое наследование отличается от закрытого и защищенного?
Answer: Имя класса может использоваться в разных местах.
Какие функции не наследуются? Почему?
Answer: Конструкторы и оператор присваивания. Потому что если они не определены, они генерируются компилятором.
Сформулируйте правила написания конструкторов в производном классе.
Answer: Если базовый класс имеет конструктор, он должен быть вызван.
Каков порядок вызова конструкторов? А деструкторов?
Answer: В обратном порядке: (стек) Базовые виртуальные классы Таблица виртуальных классов. Базовые классы. Таблица виртуальных функций. Члены Сам класс
У деструктора наоборот
Можно ли в производном классе объявлять новые поля? А методы?
Answer: Да. Да.
Если имя нового поля совпадает с именем унаследованного, то каким образом разрешить конфликт имен?
Answer: Оператор доступа к области видимости ::
Что происходит, если имя метода-наследника совпадает с именем базового метода?
Answer: Происходит перекрытие имён.
Сформулируйте принцип подстановки.
Answer: Замена в программе объектов некоторого типа на объекты подтипа этого типа не должна приводить к изменениям в поведении программы. Также подразумевается, что методы подкласса не могут выбрасывать исключений, кроме подклассов исключений, выбрасываемых базовым типом. Функции, которые используют ссылки на базовые классы, должны иметь возможность использовать объекты производных классов, не зная об этом.
Когда выполняется понижающее приведение типов?
Answer: Когда у программы плохой дизайн При передаче в функцию объекта подтипа базового.
Объясните, что такое «срезка» или «расщепление».
Answer: Случай, когда в присваивании участвует два объекта из разных уровней одной иерархии. Одному из объектов присваивается только базовая часть (то, что он знает) от другого объекта. Или, например, передача по значению в функцию класса наследника.
Объясните, зачем нужны виртуальные функции.
Answer: Для реализации динамического полиморфизма.
Что такое связывание?
Answer: Определение того, какая функция должна быть вызвана. То есть, компоновка. Тип связывания или тип компоновки определяет соответствие имени объекту или функции в программе, исходный текст которой располагается в нескольких модулях. Различают статическое и динамическое связывание. Статическое связывание бывает внешним или внутренним. Оно обеспечивается на стадии формирования исполнительного модуля, ещё до этапа выполнения программы. Если объект локализован в одном модуле, то используется внутреннее связывание. Тип компоновки специальным образом не обозначается, а определяется компилятором по контексту, местоположению объявлений и использованию спецификаторов класса памяти. Внешнее связывание выполняется компоновщиком, который на этапе сборки многомодульной программы устанавливает связь между уникальным объектом и обращениями к объекту из разных модулей программы. При динамическом связывании компоновщик не имеют никакого представления о том, какой конкретно объект будет соответствовать данному обращению. Динамическое связывание обеспечивается транслятором в результате подстановки специального кода, который выполняется непосредственно в ходе выполнения программы. В Си глобальные переменные по умолчанию имеют внутреннее связывание. В си-плюсплюс – наоборот, внешнее. И то и другое, разумеется, если квалификатор не задан явно. Ключевое слово extern выводит за пределы области видимости единицы трансляции.
Чем «раннее» связывание отличается от «позднего»?
Answer: На этапе компиляции – разрешение перегрузки. На этапе выполнения – виртуальные функции.
Какие два вида полиморфизма реализованы в С++?
Answer: Статический и динамический. Шаблоны и виртуальные функции.
Дайте определение полиморфного класса.
Answer: Класс с виртуальными функциями.
Может ли виртуальная функция быть дружественной функцией класса?
Answer: Да.
Наследуются ли виртуальные функции?
Answer: Да.
Каковы особенности вызова виртуальных функций в конструкторах и деструкторах?
Answer: В конструкторе не работают. Потому что объект ещё до конца не создан, следовательно нет таблицы виртуальных функций.
Можно ли сделать виртуальной перегруженную операцию, например, сложение?
Answer: Да.
Может ли конструктор быть виртуальным? А деструктор?
Answer: Нет. Да.
Как виртуальные функции влияют на размер класса?
Answer: Указатель на таблицу виртуальных функций для каждого объекта. Таблица виртуальных функций для каждого класса. В целом – никак.
Как объявляется «чистая» виртуальная функция?
Answer: Присваивается 0.
class A {
virtual void d() = 0;
}
Дайте определение абстрактного класса.
Answer: Класс, содержащий хотя бы одну чисто виртуальную функцию.
Наследуются ли чистые виртуальные функции?
Answer: Да.
Можно ли объявить деструктор чисто виртуальным?
Answer: Да. Деструктор должен быть определён.
Чем отличается чистый виртуальный деструктор от чистой виртуальной функции?
Answer: Он обязан иметь определение.
Зачем требуется определение чистого виртуального деструктора?
Answer: Чтобы корректно удалять объекты по ссылке типа базового класса. Например, если это единственный метод, а создание класса нужно запретить.
Наследуется ли определение чистой виртуальной функции?
Answer: Нет. У чистой виртуальной функции нет смысла в определении.
Приведите классификацию целей наследования.
Answer: Наследование интерфейса и наследование реализации.
Объясните разницу наследования интерфейса от наследования реализации.
Answer: В первом случае наследуются объявления функций, во втором – поля и алгоритмы.
Назовите причины, требующие разделения программ на части.
Answer: Уменьшение времени на компиляцию, борьба со сложностью, ODR.
Дайте определение термина «единица трансляции»?
Answer: Результат обработки препроцессором исходного файла с кодом.
Чем отличается файл с исходным текстом от единицы трансляции?
Answer: Первое – это то, что видит программист. Второе – то, что обрабатывает компилятор.
Существуют ли в С++ конструкции, позволяющие идентифицировать отдельный модуль?
Answer: Пространство имён, неименованное пространство имён.
Какие способы сборки программы вы можете назвать?
Answer: Полная перекомпоновка и частичная сборка.
Что такое «объектный модуль»? Программа, которая «собирает» объектные модули в программу, называется ______ ?
Answer: Транслированный код единицы трансляции с таблицами экспорта. Линковщик.
В чем заключается отличие аргумента «файл» от в директиве #include?
Answer: Локальные и глобальные подключения.
Что такое ODR?
Answer: Правило одного определения. One definition rule. Каждый пользовательский тип должен быть определён в программе ровно один раз.
Объясните, что такое «страж» включения и зачем он нужен.
Answer: Это конструкция из #ifdef SYMBOL Нужна для предотвращения повторного включения и соблюдения ODR
Является ли интерфейс класса его определением?
Answer: Нет. Это его объявление.
Сколько определений класса может быть в единице трансляции?
Answer: Одно.
Сколько определений класса может быть в многофайловой программе?
Answer: Несколько, если они идентичны.
Чем отличаются стандартные заголовки string, string.h и cstring?
Answer: STL, CRT, MFC
Объясните суть идиомы Pimpl.
Answer: В классе хранится указатель на реализацию. Реализация скрыта от пользователя.
Что такое делегирование и как его можно использовать для повышения степени инкапсуляции?
Answer: Это спецификация множества операций, которые не определены для одного класса и должны обслуживаться другим. Можно хранить указатель на другой класс и ссылаться на него в функциях. Разделяет сферы ответственности.
Каким образом глобальную переменную, определенную в одной единице трансляции, сделать доступной в другой единице трансляции? А константу?
Answer: определение int x = 10; доступность extern int x; определение extern const int x = 10; доступность extern const int x;
Можно ли использовать слово extern при объявлении функций?
Answer: Да.
Как локализовать объявление функции в файле?
Answer: Использовать static(внутреннюю компоновку) или неименованные пространства имён(предпочтительно)
Чем отличается «внешнее» связывание от «внутреннего» связывания?
Answer: Внутренее связывание происходит тогда, когда объект локализован в одном модуле. Внешнее связывание – установка линкером связи между объектом и обращениями к нему.
Что такое «спецификации компоновки»?
Answer: Нотация, позволяющая компоновать объектные модули, написанные на разных языках программирования. Например, соглашения о вызове функций(порядке помещения параметров в стек).
Какие объекты обладают внутренним связыванием по умолчанию?
Answer: Неглобальные переменные
Какие области видимости имен вы знаете?
Answer: Глобальная, в пределах единицы трансляции, в пределах класса, в пределах функции, в пределах тела цикла.
Для чего используются пространства имен?
Answer: Для управления сложностью и разделения программы на части. Для избегания глобальных переменных.
Чем отличаются именованные и неименованные пространства имен?
Answer: В неименованных namespaces доступ к объявленным в них именам возможен только из той же единицы трансляции, где определено namespace.
Могут ли пространства имен быть вложенными?
Answer: Да.
Для чего применяются алиасы пространства имен?
Answer: Для удобства использования и чтобы избежать конфликта имён.
Как сделать члены пространства имен доступными в нескольких (в пределе — во всех) файлах программного проекта?
Answer: using namespace xxx;
Объясните разницу между статической и динамической инициализацией.
Answer: Значение известно на этапе компиляции/выполнения.
В чем состоит проблема инициализации глобальных статических переменных?
Answer: В том, что порядок инициализации неизвестен.
Какие элементы класса можно объявлять статическими?
Answer: Методы, операторы, члены
Можно ли объявить в классе статическую константу? А константный статический массив?
Answer: Да. Да.
А какие статические поля можно инициализировать непосредственно в классе?
Answer: Статические интегральные константы.
Как определяются статические поля? В какой момент работы программы выполняется инициализация статических полей?
Answer: ключевое слово static до вызова main
Сколько места в классе занимают статические поля ?
Answer: 0
Чем отличается статический метод от обычного?
Answer: нет this
Какие методы класса не могут быть статическими?
Answer: Конструкторы, деструкторы, виртуальные функции
Какие применения статических полей вы можете привести? А каким образом применяются статические методы?
Answer: Счётчик экземпляров. Состояние класса. Функция, создающая объекты.
Приведите структуру и принцип действия паттерна Singleton.
Answer: Класс с приватным конструктором. Имеет функцию для возврата объекта. Хранит объект в себе. Общая проблема реализации паттерна – время жизни.
Для чего предназначены шаблоны?
Answer: Для реализации типонезависимых алгоритмов и для метапрограммирования.
Какие виды шаблонов в С++ вы знаете?
Answer: Шаблоны классов и шаблоны функций.
Объясните термин «инстанцирование шаблона».
Answer: Процесс генерации объявления по шаблону.
В чем разница между определением и объявлением шаблона?
Answer: В том же, что и между определением и объявлением функции или класса.
Объясните назначение ключевого слова typename.
Answer: Чтобы заявить о том, что именованная сущность – это тип. Используется, чтобы указать на вложенный в шаблоне тип, зависящий от параметра шаблона. Может быть использовано вместо слова class в объявлении шаблона.
Какие виды параметров разрешается задавать в шаблоне класса? А в шаблоне функции?
Answer: Типы(typename, class), обычные типы(int), шаблоны
Можно ли параметрам шаблона присваивать значения по умолчанию?
Answer: Да.
Может ли параметром шаблона быть другой шаблон? Каковы особенности объявления параметра-шаблона?
Answer: Да. Нужно знать тип инстанцирования шаблона.
Что такое специализация шаблона? Объясните разницу между полной и частичной специализацией.
Answer: Частный случай некоторого шаблона при фиксированной части аргументов. Частичная специализация – часть аргументов фиксирована. Полная – все аргументы фиксированы.
Разрешается ли специализировать шаблон функции?
Answer: Да, но только полностью. Частичная специализация – это перегрузка.
Может ли класс-шаблон быть вложенным в другой класс-шаблон? А в обычный класс?
Answer: Да. Да.
Можно ли объявить в классе шаблонный метод? А шаблонный конструктор?
Answer: Да. Да.
Можно ли перегружать функцию-шаблон?
Answer: Да.
Какие параметры функции-шаблона выводятся автоматически?
Answer: Простые типы. Если совпадение с типом элементов аргумента массива.
Может ли шаблон класса быть наследником обычного класса? А обычный класс от шаблона?
Answer: Да. Нет.
Объясните, что такое класс свойств (класс трактовок).
Answer: Параметры шаблонов, используемые для выбора того или иного алгоритма.
Каким образом можно использовать возможность наследования обычного класса от шаблона?
Answer: Создавать типонезависимые интерфейсы к внутреннему средству.
Может ли шаблонный конструктор быть конструктором по умолчанию?
Answer: Нет.
Для чего применяются директивы явного инстанцирования?
Answer: Чтобы соответствующий экземпляр был скомпилирован/слинкован. При объявлении инстанцированного типа.
Объясните, в чем состоят проблемы, возникающие при разделении шаблонного класса на интерфейс и реализацию?
Answer: Реализация не может быть вынесена в cpp файл (так как экспорт шаблонов не поддерживается большинством компиляторов)
Что такое «модель явного инстанцирования» и как она работает?
Answer: Реализация вынесена в cpp файл и есть явные экземпляры для нужных типов. Например, template basic_stringstring;
Может ли шаблонный класс иметь «друзей»?
Answer: Да.
Какие проблемы возникают при объявлении дружественной функции для класса-шаблона?
Answer: Заранее неизвестен тип, с которым ведётся работа.
Разрешается ли определять в классе-шаблоне статические поля? А статические методы?
Answer: Да. Да.
Что такое «инициализация нулем»?
Answer: Инициализация объекта значением 0, преобразованным в тип объекта.
Что является единицей памяти в С++? Какие требования к размеру единицы памяти прописаны в стандарте С++?
Answer: Байт. Это самая малая величина, достаточная для хранения самых маленьких базовых типов С++. Каждый байт – непрерывная последовательность бит, имеющая уникальный адрес.
В каких единицах выдает результат операция sizeof? Какие типы данных имеют размер 1?
Answer: В единицах char. все виды char, пустой объект и указатель
Какие три вида памяти входят в модель памяти С++?
Answer: Статическая Автоматическая(стек) Свободная(куча)
Сколько видов динамической памяти обеспечивает С++?
Answer: Автоматическая и свободная(стек и куча)
Какие функции для работы с динамической памятью достались С++ по наследству от С? В какую библиотеку они включены?
Answer: семейство alloc и free cstdlib
Какие функции выделяют память, и с помощью каких функций память освобождается?
Answer: malloc, free
Какое важное отличие имеет функция calloc() от функции malloc()?
Answer: Обнуление выделенной памяти.
Какие действия выполняют функции выделения памяти, если память не может быть выделена?
Answer: Возвращают NULL.
Зависит ли объем выделенной памяти от типа указателя? Влияет ли выравнивание на объем выделяемой динамической памяти?
Answer: Нет. Да.
Можно ли с помощью функции realloc() уменьшить объем выделенной памяти?
Answer: Да.
Что произойдет, если функции free() передать в качестве аргумента нулевой указатель?
Answer: Ничего.
В чем главное отличие объектно-ориентированного механизма new/delete от механизма malloc()/free()?
Answer: Он оперирует на уровне объектно-ориентированной абстракции, а не на машинном уровне. Машинная низкоуровневая модель рассматривает память как массив битов. Высокоуровневая модель, как объект.
Сколько существует форм new/delete? В чем их отличие?
Answer: new, new[], delete, delete[]
Возможность создавать массив объектов. есть операторы с размещением, есть с параметром nothrow
Какие типы являются POD-типами? Чем отличается работа механизма new/delete с POD-объектами и nonPOD-объектами?
Answer: Plain old data. Те типы, которые есть в С. Классы или структуры, содержащие только POD-поля.
new X - не инициализируется new X() - инициализируется
Какие функции выполняет обработчик new?
Answer: Пытается найти память в случае неудачи при помощи new_handler или просто возвращает исключение.
Можно ли реализовать собственный обработчик new и «прицепить» его к механизму new/delete?
Answer: Да. set_new_handler()
В чем главное отличие объединения от других видов классов С++?
Answer: Это структура, в которой все члены расположены по одному и тому же адресу.
Может ли объединение участвовать в иерархии наследования? Что такое анонимное объединение?
Answer: Нет. Объединение без имени, в области видимости структуры.
Разрешается ли определять для объединения конструкторы и деструктор? А виртуальные функции?
Answer: Нет. Нет.
В чем похожи и чем отличаются объединение и размещающий new?
Answer: Похожи моделью “конкретный кусок памяти – размещаю в нём, что хочу”. Отличаются – объединение интерпретирует кусок памяти по-разному.
Объясните, почему при использовании размещающего new нужно явным образом вызывать деструктор?
Answer: Потому что мы помещаем объект в область, которая явно не управляется стандартным распределителем памяти.
Зачем нужны интеллектуальные указатели?
Answer: Для облегчения управления памятью. Для реализаций стратегий владения.
Что такое «стратегия владения»? Сколько стратегий владения вы знаете?
Answer: Способ, которым умные указатели ссылаются на объект Глубокое копирование – копирование объекта, на который он ссылается копирование при записи – копирование объекта происходит только при первой попытке его модификации подсчёт ссылок – подсчёт указателей, ссылающихся на объект. когда 0 – сборка мусора разрушающее копирование – можно быть уверенным, что если в вызывающем модуле значение не используется, оно уничтожается. связывание ссылок – нет необходимости считать указатели, получается список владельцев(полезно при недостатке динамической памяти).
Какой интеллектуальный указатель реализован в стандартной библиотеке STL, и какую стратегию владения он реализует?
Answer: auto_ptr, разрушающее копирование
Объясните, в чем преимущества и недостатки интеллектуальных указателей со счетчиком ссылок.
Answer: Простая стратегия. Потеря скорости и неэффективность работы с памятью.
Разрешается ли перегружать new и delete и какими способами?
Answer: Разрешается. Локально, в классе и глобально.
Опишите схему функции, перегружающей глобальную функцию new.
Answer: void *operator new (size_t size); void operator delete[](void *p);
Отличается ли реализация перегруженной функции new для массивов от реализации «обычной» функции new()?
Answer: Да. Указатель на объект отличается от указателя на первый элемент массива, как в С.
Как вы думаете, почему функции new/delete, перегружаемые для класса, являются статическими?
Answer: Потому что они выделяют память для объекта, то есть, создают его.
Зачем при перегрузке new/delete для класса нужно проверять размер запрашиваемой памяти?
Answer: Потому что операторы наследуются и могут быть вызваны потомками.
Объясните, чем определяется «динамичность» контейнеров?
Answer: Распределителями памяти? Что за вопрос вообще?
Что такое «стратегия распределения памяти», и какие стратегии выделения памяти вы знаете?
Answer: Алгоритм выделения блоков памяти. Первый походящий, последний подходящий, наиболее подходящий.
Рассмотрите следующую стратегию распределения памяти: память выделяется для нескольких элементов блоками фиксированной длины, но блоки связываются в список. Для какого вида контейнера можно использовать такую стратегию?
Answer: list
Какие операции можно перегрузить для доступа к элементам двумерного массива?
Answer: ()
В чем заключаются сложности использования операции индексирования [] для доступа к элементам двумерного массива?
Answer: Необходимо знать размеры массива по обе стороны
Каковы способы реализации операций с контейнерами?
Answer: алгоритм + итератор + функтор
Какую конструкцию можно назвать «обобщенный алгоритм»?
Answer: Шаблон.
Каким образом объявить указатель на метод?
Answer: typedef char* (Someclass::*func_ptr)(int, char *);
Объясните разницу между указателем на функцию и указателем на метод.
Answer: Указатель на функцию – глобально. Указатель на метод – для конкретного типа. Указатели на методы с одинаковой сигнатурой, но для разных типов являются разными указателями.
Каким образом получить адрес метода?
Answer: &Class::somefunc;
Можно ли указателю на функцию присваивать адрес метода?
Answer: Нет.
Какие операции определены в С++ для косвенного вызова метода через указатель?
Answer: .* ->*
Что такое «функтор»? Приведите пример функционального класса.
Answer: Объект, который рассматривается, как функция. Объект класса с оператором ().
#include <iostream>
class CFunctor
{
public:
std::string
operator() (const std::string &s) {
return s;
}
};
int main() {
CFunctor f;
std::cout << f("string") << std::endl;
return 0;
}
stdout:
string
Какими способами функтор вызывается?
Answer: Передача в качестве параметра и как функция.
Можно ли использовать наследование при разработке функторов?
Answer: Да.
Разрешается ли операцию вызова функции () определять как виртуальный метод? А как статический?
Answer: Да. Нет.
В чем преимущества функторов перед указателями на функции?
Answer: поддержка обобщённого программирования сохранение данных между вызовами, плюс операции инициализации этих данных операцию () проще сделать встроенной, чем указатель на функцию
Объясните, зачем нужны адаптеры функторов? Какие виды адаптеров вы знаете?
Answer: Решают задачу, когда нужен новый предикат, который во многом похож на старый. Связыватель, адаптер методов, адаптер указателя на функцию, отрицатель
Как используются классы свойств при разработке функторов?
Answer: Позволяют настраивать функтор.
Объясните, что такое «композиция» и приведите примеры?
Answer: Это один из способов реализации отношения “содержит”. Сделать один класс членом другого. Через закрытое наследование – другой способ. Отношения “является” – открытое наследование.
Объясните, чем отличается множественное наследование от простого?
Answer: Один родитель и много родителей.
Приведите структуру и принцип действия паттерна Adapter.
Answer: Паттерн вносит ограничения или привносит функциональность в сущность, адаптируя её под работу в конкретных условиях.
Сформулируйте основную проблему множественного наследования.
Answer: Проблема неоднозначности и совокупного хранения нескольких объектов базового класса. Комбинаторное по своей природе, множественное наследование нельзя применять для поддержки многовариантных систем. Нарушает принцип подстановки.
Выполняется ли принцип подстановки при открытом множественном наследовании?
Answer: Да, если наследование проведено корректно.
Что такое виртуальное наследование? Каковы его преимущества и недостатки по сравнению с обычным наследованием?
Answer: Это множественное наследование, при котором нескольким классам, имеющим один базовый всегда сопоставляется один объект этого базового класса. Преимущества – экономия памяти.
Может ли виртуальное наследование быть одиночным?
Answer: Да.
Влияет ли виртуальное наследование на размер класса?
Answer: Да. Уменьшает размер итогового класса.
Объясните, каким образом с помощью виртуального наследования можно вообще запретить наследование.
Answer: class my_class;
class nonderivable
{
friend class my_class;
private:
nonderivable() { }
};
class my_class: public virtual nonderivable
{
public:
my_class(){}
};
class DD : public my_class { };
int main()
{
my_class a;
DD dd; // error
return 0;
}
Какие средства С++ составляют RTTI?
Answer: dynamic_cast, typeid
Объясните разницу между повышающим, понижающим и перекрестным приведением.
Answer: Приведение к базовому типу, к потомку, к родственному классу в иерархии
Какими свойствами должен обладать класс, чтобы с ним работал механизм RTTI?
Answer: Быть полиморфным типом.
В чем приведение указателей отличается от приведения ссылок?
Answer: dynamic_cast генерирует исключение, если ссылка, если указатель, то просто возвращает 0
Какие исключения связаны с механизмом RTTI?
Answer: Можно отлавливать целую группу исключений и определять, что это за подтип базового типа при помощи dynamic_cast
Что такое «поток» — дайте определение.
Answer: Часть процесса, выполняющаяся параллельно (или псевдопараллельно). Средство ввода-вывода С++
Как классифицируются потоки, реализованные в библиотеках ввода/вывода С++?
Answer: По направлению: ввод, вывод По типу объекта: строковые, файловые
Что такое буферизация и зачем она нужна?
Answer: Это техника оптимизации, заключающаяся в накапливании достаточно большой порции данных и последующей их передаче на обработку.
Какие библиотеки ввода/вывода реализованы в С++ и чем они отличаются?
Answer:
- ОО модель
- С модель
Перечислите стандартные потоки и объясните их назначение.
Answer: stdin, stdout, stderr идентификаторы 0,1,2
Зачем нужен процесс форматирования и когда он выполняется?
Answer: Он нужен для ввода-вывода данных с наложенными на них ограничениями. Выполняется тогда, когда мы работаем с данными ожидаемой структуры.
Что такое «форматная строка», и в каких функциях она используется?
Answer: Строка, задающая формат интерпретации переменных. Используется в вводе-выводе в стиле С.
Объясните назначение элементов спецификатора формата.
Answer: указывают тип, поля и точность вывода % – указывает, что следующий за ним символ будет спецификатором формата g – точность вывода числа с плавающей точкой определяется реализацией e – научный формат вывода f – фиксированный знак после запятой i – целое число c – символ s – строка
Сколько спецификаторов формата может быть в форматной строке?
Answer: Сколько угодно (на сколько памяти хватит)
Какой из элементов спецификатора формата не является умалчиваемым?
Answer: Ширина поля #
Перечислите несколько известных вам обозначений типов в спецификаторе формата, и укажите их назначение.
Answer: g – точность вывода числа с плавающей точкой определяется реализацией e – научный формат вывода f – фиксированный знак после запятой i – целое число c – символ s – строка
Сколько модификаторов типа вы знаете, и какую роль модификатор типа играет в спецификаторе формата?
Answer: c,d,i,o,x,f,e,g
С помощью какого флага можно выровнять выводимое значение влево? А каким образом вывести ведущие нули?
Answer: ios_base::left ios_base::showpoint
Какое действие оказывают на выводимую строку ширина, точность и флаги в спецификаторе формата?
Answer: Ширина – не менее стольки-то симолов. Точность – забивает нулями недостаток, флаги – выравнивают
Для чего в спецификаторе формата может использоваться символ звездочка («*»)? Чем отличается действие этого символа при воде и при выводе?
Answer: Ширина поля. При вводе “не менее стольки-то” при выводе обрезает.
Каковы особенности ввода строк?
Answer: Если вводить в массив символов, а не в str – риск переполнения. Строка считывается до символа-разделителя, поэтому чтобы считать строку с пробелами, необходимо использовать getline() Символы-разделители определяются isspace(), которая определена в
Каким образом ограничить набор вводимых символов при вводе?
Answer: get(буфер, количество символов) или через строковые потоки – применить флаг
Что является главной проблемой при использовании форматного ввода/вывода из библиотеки ?
Answer: Переполнение буфера, так как это функции с переменным количеством аргументов.
Объясните, для чего нужны строковые потоки. Почему строковые потоки – всегда форматируемые?
Answer: Для форматирования в памяти.
С помощью каких функций выполняется работа со строковыми потоками?
Answer: operator << operator >>
Можно ли использовать тип string (и каким образом) со строковыми потоками?
Answer: Получить итоговую строку.
Объясните, в чем заключается различие между текстовым и двоичным файлом.
Answer: В интерпретации содержимого. Двоичный файл – “сырые данные”, свободная интерпретация. Тестовый файл – определённая структура (фиксированный набор символов, символы-разделители, и.т.д.) Текстовый файл можно интерпретировать, как двоичный, а вот двоичный, как текстовый – не всегда.
Объясните, что означает «открыть» файл и «закрыть» файл?
Answer: Связать файл с дескриптором, который обращается к буферу в памяти, куда подгружается содержимое файла с диска. В двух словах “перекачать файл в память с диска.
Каким образом внешний файл связывается с потоком?
Answer: Созданием объекта класса.
Можно ли один и тот же поток связать с разными файлами? А один и тот же файл с разными потоками?
Answer: Нет. Да, с флагами общего доступа.
Перечислите режимы открытия файла. Чем отличается режим “r” от режима “a”?
Answer: r a w и их комбинации первый читает, но не пишет, второй может читать и добавлять в конец
Какую роль в режиме открытия играет знак плюс («+»)?
Answer: Добавление в конец
В каких случаях необходимо следить за ситуацией «конец файла»? Каким способом это делается?
Answer: Когда идёт поблоковое чтение. Специальной функцией или итератором, смотря что используется.
Можно ли текстовый файл открыть как двоичный? А двоичный — как текстовый?
Answer: Да.Да.
Какие функции ввода/вывода используются для обмена с текстовыми файлами?
Answer: fprintf fscanf
Перечислите функции ввода/вывода для работы с двоичными файлами.
Answer: fget fput
Какие функции реализованы в библиотеке для обеспечения прямого доступак записям двоичного файла? Можно ли их использовать для работы с текстовыми файлами?
Answer: fseek, fread, fwrite Можно.
Объясните назначение функции fseek().
Answer: Передвигать текущую позицию чтения-записи в файле.
Чем отличается функция ftell() от функции fgetpos()?
Answer: Первая ищет смещение от начала потока, вторая может искать от конца, начала и текущей позиции.
Объясните, что означает «перенаправление» потока? Какие потоки можно перенаправлять и куда?
Answer: Вывод данных на другое заданное устройство, нежели предполагалось вначале. поток ввода, поток вывода, поток ошибок. На любое блочное или символьное устройство, будь то терминал или файл. С точки зрения С++ – это копирование буфера. любой в любой
Каким образом перенаправление ввода можно использовать для ввода строк с пробелами?
Answer: Перенаправить в стрингстрим.
В чем преимущества объектно-ориентированной библитеки по сравнению с процедурной?
Answer: Если имеется в виду вывод – безопасность с точки зрения типов и общий стиль для вывода как встроенных, так и пользовательских типов.
В каких состояних может находиться поток? Каким образом отслеживается состояние «конец потока»?
Answer: good, bad, fail итератором
Какие объектно-ориентированные потоки связаны со стандартными потоками?
Answer: cin, cout, cerr, clog
Чем отличаются объектно-ориентированные строковые потоки от процедурных строковых потоков?
Answer: Нет опасности переполнения
Каким образом строковые потоки можно использовать для ограничения ширины поля ввода? А можно ли с той же целью использовать строковые потоки ?
Answer: задать width() Да. Спецификатор *
Сравните средства форматирования объектно-ориентированной и процедурной библиотеки.
Answer: ОО – манипуляторы. Действуют для потока. ПРОЦ – строка форматирования. Действует для конкретного ввода.
Каким образом ввести строку типа string с пробелами?
Answer: Функция getline()
Каково назначение флагов форматирования? Какие средства реализованы в библиотеке для работы с флагами форматирования?
Answer: Управляют форматированием ввода-вывода.
Что такое «манипулятор»? В чем преимущества манипуляторов перед флагами форматирования?
Answer: Функция для установки состояния потока. Когда операции записываются, как инструкции, логические связи между ними не очевидны.
Как связываются файлы с потоками в объектно-ориентированной библиотеке?
Answer: Через конструктор и деструктор.
Можно ли файлы, записанные функциями библиотеки, прочитать объектно-ориентированными средствами? А наоборот?
Answer: Да. Да.
Перечислите режимы открытия объектно-ориентированных файловых потоков. каким образом комбинируются режимы открытия файлоавых потоков?
Answer: чтение, запись, добавление в конец, двоичный, поиск конца побитовым “ИЛИ”
Обязательно ли закрывать файл, связанный с объектно-ориентированным файловым потоком? А открывать?
Answer: Достаточно создать объект класса потока.
Каким образом открыть файловый поток для чтения и записи одновременно?
Answer: объявить объект типа std::fstream и добавить соответствующие флаги.
Как открыть файловый поток для дозаписи?
Answer: ios_base app
Можно ли вывести значение переменной в двоичном виде и как это сделать?
Answer: int i = 938745; cout << bitset(i);
Или через флаги.
Разрешается ли наследовать от классов библиотеки ввода/вывода?
Answer: Да.
Каким образом можно перенаправить объектно-ориентированный поток?
class redirecter
{
public:
redirecter(
std::ostream &dst,
std::ostream &src
) :
src (src),
sbuf(src.rdbuf(dst.rdbuf()))
{}
~redirecter() {
src.rdbuf(sbuf);
}
private:
std::ostream & src;
std::streambuf * const sbuf;
};
int main()
{
std::ofstream log("hello-world.log");
redirecter redirect(log, std::cout);
std::cout << "Hello, world!\n";
return 0;
}
Как используется буфер потока для копирования потока?
Answer: std::ostream dst; std::istream src; dst << src.rdbuf();
Какими операциями выполняется форматированный ввод/вывод в файловые потоки? А неформатированный?
Answer: fprintf, fscanf fread, fwrite
Реализованы ли в объектно-ориентированной библиотеке средства прямого доступа к файловым потокам? Сравните их с аналогичными средствами библиотеки.
Answer: Да.
С какими объектно-ориентированными потоками разрешается, и с какими не разрешается использовать средства прямого доступа?
Answer: iostream – разрешено stringstream – запрещено
Покажите, каким образом можно выполнить перегрузку операций ввода/вывода для нового типа данных.
class A
{
public:
int getField() { return i; }
int setField(int i) { this->i = i; }
A (int i) : i(i);
private:
int i;
};
Answer: std::ostream& operator << std::istream& operator >> (std::istream &s, A a) { int i = 0; s >> i; a.setField(i); return s; }
Как выполняется обработка ошибок ввода/вывода в объектно-ориентированной библиотеке?
Answer: Исключения или флаги состояния потока
Какое стандартное исключение генерируется при ошибках ввода/вывода? Обязательно ли оно генерируется?
Answer: ios_base::failure не всегда.
Надо попросить об этом, например,
cout.extensions(ios_base::badbit|ios_base::eofbit);
Чем стандартные широкие потоки отличаются от узких?
Answer: Оперируют расширенными символами (юникод) wchar_t, например.
Что такое — «локаль», и каково ее назначение?
Answer: Объект, который контролирует классификацию символов и устанавливает порядок их следования. Поддержка соглашений, принятых в другой культуре.
Как установить русский шрифт при выводе в консольное окно?
Answer: например: locale loc(“ru_RU.UTF8?); locale::global(loc);
отличается ли ввод/вывод широких файловых потоков от узких? Чем?
Answer: На уровне интерфейса – нет.
Перечислите все последовательные контейнеры стандартной библиотеки. Чем они отличаются друг от друга?
Answer: vector – непрерывная в памяти последовательность, операции с концом вектора и индексация О(1), вставка в середину O(n) list – вставка в начало и конец за постоянное время, доступ к элементам за линейное dequeue – вставка в оба конца за постоянное время, индексация приближённо как у вектора
Перечислите адаптеры последовательных контейнеров и дайте их подробную характеристику.
Answer: queue, stack, priority_queue
Почему для адаптеров-очередей нельзя использовать вектор в качестве базового?
Answer: У вектора нет pop_front
Чем простая очередь queue отличается от приоритетной очереди priority_queue?
Answer: В приоритетной очереди операция извлечения ориентируется не только на положение элемента, но и на результат операции сравнения.
Каким требованиям должны удовлетворять элементы контейнера?
Answer: Должна поддерживаться операция копирования, похожая на встроенные типы. Для ассоциативных контейнеров должно поддерживаться сравнение (по умолчанию
Могут ли быть указатели элементами контейнера? А итераторы?
Answer: Да. Теоретически может, есть опасность получить неверный итератор.
Почему нельзя использовать в качестве элементов контейнера стандартный интеллектуальный указатель auto_ptr?
Answer: Потому что операция копирования auto_ptr зануляет исходный указатель
Зачем в контейнере list реализованы собственные методы сортировки поиска и слияния? Можно ли пользоваться соответствующими стандартными алгоритмами при обработке списка?
Answer: Потому что алгоритм sort требует итератора с произвольным доступом. Нельзя.
Перечислите типовые виды конструкторов, с помощью которых можно создавать последовательный контейнер.
Answer: Конструктор копирования, конструктор заполнения.
Можно ли инициализировать контейнер элементами встроенного массива? А элементами другого контейнера? Какими способами это можно сделать?
Answer: Да.Да.Шаблонным конструктором.
Почему конструктор инициализации, параметрами которого являются итераторы, сделан шаблонным во всех контейнерах?
Answer: Чтобы можно было инициализировать вектор из другой последовательности, например, из списка.
Какие методы реализованы в контейнере-векторе для доступа к элементам?
Answer: Операция индексации, итераторы начала и конца
Отличается ли функция at() доступа по индексу от перегруженной операции индексирования и чем?
Answer: Да. Проверкой границ доступа.
Перечислите методы контейнера deque, относящиеся к определению размеров контейнера.
Answer: empty max_size size
Чем метод size() отличается от метода capacity()? А в чем отличие этих методов от метода max_size()?
Answer: сколько хранит, сколько может хранить до изменения размера памяти, сколько вообще может хранить.
Перечислите методы контейнера list, предназначенные для вставки удаления и замены элементов. Отличаются ли эти методы от соответствующих методов вектора и дека?
Answer: insert push_front pop_front push_back pop_back
нет, только некоторых методов у вектора нет.
Каким образом выполняются операции сравнения контейнеров?
Answer: Для сравнения используется оператор
Разрешается ли изменять элемент ассоциативного контейнера, доступный в данный момент по итератору?
Answer: Да, но только значение. Тонкий момент: элемент – это пара “ключ-значение”, хранение происходит в порядке неубывания ключей.
Какие контейнеры называются ассоциативными и почему?
Answer: set, multiset, map, multimap. Потому что значение ищется не по порядковому номеру, а по ключу, ассоциируя значение с ключом.
Чем контейнер map отличается от контейнера multimap?
Answer: Можно хранить уникальные или неуникальные значения.
Объясните, почему в ассоциативных контейнерах нельзя изменять элемент, доступный в данный момент по итератору.
Answer: Можно безбоязненно изменять значение, но не ключ.
По каким причинам в контейнере-множестве не реализованы типовые операции объединения, пересечения, разности и другие?
Answer: Это было бы не универсально. Вместо этого реализованы соответствующие алгоритмы на операциях со множествами. set_union set_intersection set_difference set_symmetric_difference
Как используется структура-пара в ассоциативных контейнерах?
Answer: Как единица хранения данных.
Объясните, что такое «критерий сортировки», и каким требованиям он должен удовлетворять? Какой критерий сортировки принят по умолчанию?
Answer: Это оператор сравнивания. Он должен определить строгое слабое упорядочивание, меньше и равно должны быть транзитивными.
Какими преимуществами обладает функция make_pair() по сравнению с конструктором pair()?
Answer: Не надо указывать типы, они выводятся, так как это шаблон функции.
Почему в контейнерах-отображениях операция индексирования перегружена, а в контейнерах-множествах — нет?
Answer: Незачем.
Какие гарантии безопасности обеспечивают контейнеры стандартной библиотеки?
Answer: Вектор – базовую. Остальные – не знаю.
Что такое «транзакционная гарантия безопасности» и чем она отличается от базовой?
Answer: Базовая гарантия – даже при наличии исключений, утечка ресурсов отсутствует. Транзакционная гарантия – если операция прекращается, то состояние программы остаётся неизменным. В транзакционной гарантии все ссылки и итераторы будут действительными, в базовой – нет.
Можно ли с помощью алгоритма for_each() изменить элементы контейнера?
Answer: Можно через функтор, который передаётся в алгоритм, создать новый контейнер с изменёнными элементами. Но в целом нельзя.
Что такое «распределитель памяти» и зачем он нужен?
Answer: Это абстракция. Любой тип, ведущий себя, как распределитель памяти, является им. Нужен для управления памятью для конкретного типа. это чистая абстракция. объект класса. ключевые концепции – своя стратегия выделения памяти и избавление разработчика от необходимости иметь дело с сырой памятью.
Чем отличается битовый вектор bitset от битового вектора vector?
Answer: Поддерживает операции с битами, длина фиксирована. Проиндексирован. Итератор на vectorне является булевым типом.
Дайте определение итератора.
Answer: Итератор – это чистая абстракция. Всё, что ведёт себя, как итератор – это и есть итератор. Иными словами, это объект, п оддерживающий определённый набор операций.
Ключевые концепции Текущий указываемый элемент. Операция инкремента. Равенство.
Что такое «начальный» итератор и «конечный» итератор? Какие методы, связанные с итераторами, обязательно включает каждый контейнер?
Answer: Указывает на начало последовательности и на элемент сразу за конечным в последовательности. begin, end
Чем константный итератор отличается от неконстантного?
Answer: Нельзя менять значение, доступное по итератору.
Объясните, что такое «недействительный» итератор. В каких случаях итераторы становятся недействительными?
Answer: Итератор не указывающий на элемент в контейнере или итератор за последним элементом. В случае изменения размера контейнера.
Какие категории итераторов вы знаете? Какие операции обязательно реализуются для всех категорий итераторов?
Answer: Чтения, записи, однонаправленный, двунаправленные, произвольного доступа Текущий указываемый элемент. Операция инкремента. Равенство.
К какому виду итераторов можно отнести встроенный указатель и почему?
Answer: Произвольного доступа. Потому что реализует необходимый набор операций и эти операции выполняются за постоянное время.
Какие вспомогательные функции для итераторов вы знаете? В каких случаях оправдано их применение?
Answer: advance() distance() чтобы выразить расстояние между элементами в контейнерах, где произвольный доступ выполняется за линейное время. Оправдано на небольших контейнерах.
Какие адаптеры итераторов реализованы в библиотеке?
Answer: reverse iterators.
Объясните, почему итераторы реализованы как вложенные классы в контейнерах.
Answer: Чтобы определять последовательности, необходимо строго контролировать типы итераторов.
Чем отличаются итераторы вставки от обычных итераторов?
Answer: Позволяют избежать переполнения контейнеров, изменяя их размеры.
Каким образом используются потоковые итераторы?
Answer: Это итераторы однонаправленного доступа, которые образуют интерфейс для потока, как контейнера.
Какие стандартные функторы реализованы в библиотеке STL? Каково их основное назначение?
Answer: логические и арифметические операции когда имеешь дело с числовыми или логическими классами
Для чего нужны адаптеры функторов bind1st() и bind2nd()?
Answer: вызывают бинарную функцию в качестве первого и второго аргументов соответственно
Как применяются адаптеры-отрицатели?
Answer: Возвращают противоположный предикат.
Почему алгоритмы remove() не удаляют элементы из контейнеров? Как реально удалить элементы из контейнера?
Answer: Потому что тогда изменится размер контейнера и сделает недействительными итераторы. Дорогая операция. Необходимо явно его сжать и укоротить erase();
Чем отличается стабильная сортировка от обычной?
Answer: Не меняет местами одинаковые элементы. Гарантированная скорость в худших случаях.
Какую функцию выполняет алгоритмы unique()?
Answer: Удаляют соседние равные элементы сортированного контейнера.
Могут ли стандартные алгоритмы работать со строками?
Answer: Да.
Нужно ли сортировать ассоциативные контейнеры?
Answer: Нет.
Можно ли алгоритмы для работы с множествами применять для последовательных контейнеров? При каких условиях?
Answer: Можно только для сортированных последовательностей, иначе результат не будет подчиняться правилам работы с последовательностью.
Какие алгоритмы предназначены для заполнения контейнера значениями? С какими контейнерами они могут работать?
Answer: Модифицирующие transform fill generate replace reverse
Каким образом заполнить с помощью алгоритма generate() последовательный контейнер, не имеющий ни одного элемента?
Answer: Использовать inserter
Перечислите алгоритмы, предназначенные для операций с каждым элементом контейнера.
Answer: for_each transform copy replace fill
Что обозначают аббревиатуры- vtbl, vptr
Answer: таблица виртуальных функций. в gcc располагается в начале класса.
Что такое виртуальный базовый класс.
Answer: в тех случаях, когда необходимо предоставить общий базовый класс, необходимо использовать виртуальный базовый класс.
Какое наследование по умолчанию в этих случаях
class XX: B{}
struct YY: B{}
Answer: public private
когда необходим виртуальный деструктор?
Answer: для полиморфных типов, чтобы знать, какой тип уничтожать.
В каком порядке инициализируются глобальные переменные? переменные класса?
Answer: порядок объявления в пределах единицы трансляции. в порядке объявления.
Что такое спецификация шаблонов: полная и частичная
Answer: Описание отличного от стандартного поведения при заданных частных случаях инстанцирования шаблона.
Как выглядит инициализатор для структуры?
Answer: Так же, как и для массива (и класс им можно инициализировать)
Какие виды шаблонов могуть быть аргументами других шаблонов?
Answer: только шаблоны классов
Каким свойством должен обладать объект, чтобы его можно было добавить в ассоциативные контейнеры в качестве ключа?
Answer: Т.к. значения в ассоциативных контейнерах хранятся отсортированными, то объект должен реализовывать оператор сравнения <, а остальные операторы сравнения могут быть выражены через него.
struct Foo
{
int i;
};
inline bool operator < (const Foo & lhs, const Foo & rhs)
{
return lhs.i < rhs.i;
}
std::set<Foo> data;
Foo a, b, c;
a.i = 7;
b.i = 1;
c.i = 6;
data.insert( a );
data.insert( b );
data.insert( c );
// Теперь в data элементы находятся в последовательности 1 6 7
Без реализации operator < объект класса Foo нельзя было бы добавить в set, т.к. был бы не определен их порядок внутри set'а. Для нахождения элемента в контейнеры STL сам определяет недостающие операторы из оператора меньше. Так например, чтобы вычислить нужный элемент STL проверяет меньше ли он текущего, далее больше ли, и если оба условия ложны значит элемент эквивалентен искомому. Помимо этого, для контейнеров может быть определен класс сравнения (Comparison class), в котором будет определена логика сравнения объектов, реализованная через тот же оператор меньше.
Сколько в памяти занимает произвольная структура?
Answer: sizeof всех членов + остаток для выравнивания(по умолчанию выравнивание 4 байта) + sizeof указателя на vtable (если есть виртуальные функции) + указатели на классы предков, от которых было сделано виртуальное наследование (размер указателя * количество классов)
struct Foo
{
int i;
char a;
};
int size = sizeof(Foo); // 8 байт, хотя int + char = 5.
// Все дело в дефолтном выравнивании равном 4,
// т.е. размер должен быть кратен блоку в 4 байта.
// Установим выравнивание в 1 байт
#pragma pack(push, 1)
struct Foo
{
int i;
char a;
};
#pragma pack(pop)
int size = sizeof(Foo); // 5 байт
Как сгенерировать pure virtual function call исключение?
Answer: Нужно вызвать чисто виртуальный метод в конструкторе родительского класса т.е. до создания дочернего, в котором этот метод реализован. Т.к. современный компилятор не даст это сделать напрямую, то нужно будет использовать промежуточный метод.
class Base
{
public:
Base() {
base_func();
}
void base_func() {
func(); // pure virtual function call exception
}
virtual void func() = 0;
};
class Derived : public Base
{
public:
virtual void func() {
}
};
Что дают разные модификаторы при наследовании?
Answer: Изменяют зону видимости членов базового класса.
class Base
{
public:
int i;
};
class Derived : private Base
{
// i теперь имеет модификатор доступа private
};
class Derived2 : private Derived
{
public:
Derived2() {
i = 2; // error C2247: 'Base::i' not accessible because 'Derived'
// uses 'private' to inherit from 'Base'
}
};
Derived d;
d.i; // error C2247
При private наследовании protected и public члены становятся private. При protected наследовании public становится protected. А при public ничего не изменяется.
Что такое чисто виртуальный метод и абстрактный класс?
Answer: Чисто виртуальный метод — это метод, у которого отсутствует реализация. Абстрактный класс — это класс имеющий хотя бы один чисто виртуальный метод. Как следствие, экземпляр подобного класса не может быть создан т.к. отсутствует реализация виртуального метода.
// Абстрактный класс
class Foo
{
public:
// Чисто виртуальный метод
virtual void func() = 0;
};
class Bar : public Foo
{
public:
virtual void func()
{
}
};
Foo f; // error C2259: 'Foo' : cannot instantiate abstract class
Bar b; // Ok
Для чего используется вызов throw без аргументов?
Answer: Для повторного возбуждения предыдущего исключения и направления его следующему обработчику.
try {
//....
try {
// Call something
}
catch (const std::exception &) {
// Make/Check something..
throw; // Пересылаем исключение следующему обработчику
}
//...
}
catch (const std::exception &e) {
std::cout << e.what() << std::endl;
}
Что стоит учитывать при использовании auto_ptr?
Answer: Так как данный умный указатель реализует подход разрушающего копирования, то при присвоении его другому умному указателю оригинальный потеряет свое значение. А так же его нельзя использовать в стандартных STL контейнерах.
std::auto_ptr<Foo> a(new Foo);
std::auto_ptr<Foo> b;
b = a; // a больше не ссылается на Foo
std::vector<std::auto_ptr<Foo>> v;
v.push_back(b); // error C2558: class 'std::auto_ptr<_Ty>' :
// no copy constructor available or copy constructor
// is declared 'explicit'
Также ввиду того, что в деструкторе auto_ptr вызывается оператор delete, нельзя хранить объекты созданные при помощи new. Именно из-за этого нюанса boost предоставляет умные указатели в двух вариантах, для просто объекта и их коллекции, например, shared_ptr и shared_array.
Для чего используется ключевое слово volatile?
Answer: Для указания компилятору, что доступ к переменной может осуществляться из мест, неподконтрольных ему. А как следствие, что работу с данной переменной не нужно подвергать разного рода оптимизациям.
volatile int i = 1; // Независимо от прочего кода,
// данная переменная не будет оптимизирована.
Т.е. если volatile присутствует в каком-то условии, которое не меняется со временем, то компилятор может оптимизировать его, чтобы избежать ненужных проверок, при использовании volatile компилятор скорее всего не будет этого делать.
while (1) {
if (i == 1){
// Какой-то код не изменяющий i
}
}
// Если бы volatile отсутствовало, то компилятор мог бы переделать код на что-то:
if (i == 1) { // Нет необходимости проверять i все время,
// если и так известно, что оно не изменяется
while (1) {
// Какой-то код не изменяющий i
}
}
explicit для чего нужен?
Answer: Запрещает неявное преобразование и соответсвенно избежания вызова конструктора копии. Используется в конструкторах с одним параметром. explicit указывает на требования явной формы конструктора копий.
class SomeClass {
public:
SomeClass(int value);
};
int main() {
SomeClass a(5);
SomeClass b = 5; // если конструктор explicit, то тут будет ошибка
// не вызывается конструктор копирования
return 0;
}
Что будет выведено в результате работы программы?
#include <iostream>
class A {
public:
virtual void print() const {
std::cout << "A";
}
};
class B : public A {
public:
virtual void print() const {
std::cout << "B";
}
};
void print(A a) {
a.print();
}
int main()
{
A a;
B b;
print(a);
print(b);
return 0;
}
Answer: AA
// Описать работу данного кода
#include <iostream>
template <int N>
class CPrint {
public:
CPrint() {
std::cout << N << std::endl;
CPrint<N - 1>();
}
};
template <>
class CPrint<1> {
public:
CPrint() {
std::cout << "1" << std::endl;
}
};
int main()
{
CPrint<12> p;
return 0;
}
Answer: 12 11 10 9 8 7 6 5 4 3 2 1
// Описать работу данного кода
#include <iostream>
template<bool>
struct static_assert;
template<>
struct static_assert<true> {
};
template<typename T>
void func(T) {
static_assert<false>();
}
template<>
void func(int) {
std::cout << "func(int)" << std::endl;
}
template<>
void func(double) {
std::cout << "func(double)" << std::endl;
}
int main() {
func(1.0);
func(500);
func("b");
return 0;
}
Используя директиву #define, как бы вы описали именованную константу, которая возвращает число секунд в году? Високосными годами следует пренебречь.
#define SECONDS_PER_YEAR (60UL * 60UL * 24UL * 365UL)
Здесь я смотрю на несколько моментов:
(а) Базовое знание синтаксиса #define (т.е. отсутствие точки с запятой в конце, необходимость заключать в круглые скобки и т.д.). (b) Правильный выбор имени - с применением заглавных букв и подчёркиванием. (c) Понимание того, что препроцессор будет вычислять для вас константное выражение. (d) Понимание того, что выражение переполнит integer аргумент на 16-и битной машине - а следовательно потребность в L, указывающей компилятору обращаться с выражением как с Long. (е) А если вы в добавок написали выражение с UL (обозначающее unsigned long), то вы отлично начали тест, потому что показываете, что знаете об опасности типов со знаками и без, и запомните - первое впечатление считается!
Напишите «стандартный» макрос MIN. То есть, макрос, который берет два аргумента и возвращает меньший из них.
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
Цель этого вопроса - проверить следующее:
(а) Умение использовать директиву #define для написания макросов. Это важно, потому что до того, как inline оператор стал частью Си стандарта, макросы были единственным способом генерирования встраиваемого кода. А такой код часто бывает необходим для достижения требуемого уровня производительности. (b) Знание троичного условного оператора. Он используется в Си, потому что позволяет компилятору производить потенциально более оптимальный код, чем последовательность if-else. Производительность обычно является важной составляющей во встраиваемых системах, поэтому необходимо знать и уметь использовать эту конструкцию. (с) Понимание необходимости заключать аргументы макросов в скобки. (d) Я также использую этот вопрос, чтобы начать разговор о побочных эффектах макросов. Например, о том, что происходит, когда вы пишите такой код:
least = MIN(*p++, b);
Каково назначение директивы препроцессора #error?
Обычно только ботаники читают приложения к руководствам по Си, чтобы узнать о таких вещах.
При программировании встраиваемых систем часто используются бесконечные циклы. Как реализовать бесконечный цикл в Си?
Существует несколько решений этой проблемы. Я предпочитаю такое:
while(1) { … }
Другая общепринятая конструкция выглядит так:
for( ; ; ) { … }
Лично я не люблю эту конструкцию, потому что такой синтаксис не объясняет, что происходит. Если кандидат предлагает именно это решение, я пытаюсь выяснить, чем он обосновывает свои действия. Если ответ сводится к тому, что - «Меня научили так делать, и я никогда об этом с тех пор не думал» - это говорит не в пользу кандидата. С другой стороны, если он заявляет, что Керниган и Ритчи предпочитали этот метод, и это единственный способ для бесконечного цикла пройти контроль на соответствие стандартам, то он получает дополнительные очки.
Третье решение заключается в использовании goto:
Loop: … goto Loop;
Кандидаты, которые предлагают этот вариант, являются либо программистами на языке ассемблера, либо они оторванные от жизни программисты Бейсика/Фортрана, ищущие новое поле для деятельности.
Используя переменную «a», запишите объявления для:
(а) Целого (b) Указателя на целое (с) Указателя на указатель на целое (d) Массива из десяти целых (е) Массива из десяти указателей на целые (f) Указателя на массив из десяти целых (g) Указателя на функцию, которая принимает целочисленный аргумент и возвращает целое (h) Массива из десяти указателей на функции, которая принимает целочисленный аргумент и возвращает целое
Answer:
(a) int a; // Целое (b) int *a; // Указатель на целое (c) int **a; // Указатель на указатель на целое (d) int a[10]; // Массив из десяти целых (e) int *a[10]; // Массив из десяти указателей на целые (f) int (*a)[10]; // Указатель на массив из десяти целых (g) int (*a)(int); // Указатель на функцию, которая берет целый аргумент // и возвращает целое (h) int (*a[10])(int);// Массив из десяти указателей на функции, // которые берут целый аргумент и возвращают целое
Люди часто утверждают, что на некоторые из этих вопросов они обычно ищут ответы в руководствах – согласен. Во время написания этой статьи я сверялся с руководствами, чтобы убедиться, что синтаксис является верным. Однако во время своего интервью я обычно ожидаю, что мне зададут подобный вопрос. Поэтому я должен быть уверен, что у меня есть ответы, по крайней мере, на несколько часов интервью. Кандидаты, которые не знают ответов (или, по крайней мере, большую их часть), просто не готовы к интервью. Если они не могут быть готовы к интервью, к чему они могут быть готовы вообще?
В каких случаях используется ключевое слово static?
Полностью отвечают на этот вопрос довольно редко. Спецификатор static в языке Си используется в трёх случаях:
(а) Переменная, описанная внутри тела функции как статическая, сохраняет свое значение между вызовами функции. (b) Переменная, описанная как статическая внутри модуля, но снаружи тела функции, доступна для всех функций в пределах этого модуля и не доступна функциям любых других модулей. То есть, это локализованная глобальная переменная. (с) Функции, описанные внутри модуля как статические, могут быть вызваны только другими функциями из этого модуля. То есть, область видимости функции локализована модулем, внутри которого она описана.
Большинство кандидатов отвечают правильно на первую часть. Умеренное число кандидатов справляется со второй частью, ну и небольшое количество понимают ответ (с). Это серьёзный недостаток кандидата, если он не понимает важность и преимущества ограничения области видимости данных и кода.
Что означает ключевое слово const?
Как только интервьюируемый говорит: «Const - значит константа», я понимаю, что имею дело с непрофессионалом. Дэн Сакс в прошлом году дал исчерпывающее объяснение спецификатору const, так что каждый читатель ESP должен быть досконально ознакомлен с тем, что const может сделать для вас и чего он не может. Если вы не читали эту рубрику, достаточно будет сказать, что const означает «только для чтения». Хотя этот ответ не совсем справедливо отражает предмет разговора, я бы принял его в качестве правильного.
Если кандидат даст правильный ответ, то я задам ему следующие дополнительные вопросы:
Что означают следующие объявления?
const int a; int const a; const int *a; int * const a;
const int * const a;
Первые два объявления означают одну и ту же вещь, а именно: «а» - это целочисленная константа (только для чтения). Третье означает, что «а» является указателем на целочисленную константу. Четвёртое описывает «а» как константный указатель на целое. И последнее объявление — константный указатель на целочисленную константу.
Если кандидат правильно ответит на эти вопросы, я буду впечатлён. В данном случае он может поинтересоваться, почему я делаю такой упор на спецификатор const, так как очень легко написать правильно функционирующую программу, не используя его ни разу. Существует несколько причин:
(а) Использование спецификатора const сообщает полезную информацию тому, кто читает ваш код. Фактически, объявление параметра как const, говорит пользователю о его предполагаемом использовании. Если вы когда-нибудь тратили много времени, устраняя неразбериху, оставленную другими людьми, то вы быстро научитесь ценить эту дополнительную информацию. (Конечно, программисты, использующие const, редко оставляют после себя путаницу, которую приходится устранять другим…) (b) Const сообщает оптимизатору некоторую дополнительную информацию, что потенциально позволяет генерировать более оптимальный код. (с) Код, в котором используется спецификатор const, проявляет тенденцию к меньшему количеству ошибок.
Что означает ключевое слово volatile? Приведите три различных примера его использования.
Ключевое слово volatile информирует компилятор о том, что переменная может быть изменена не только из текущего выполняемого кода, но и из других мест. Тогда компилятор будет избегать определенных оптимизаций этой переменной.
Примеры volatile переменных:
(а) Регистры в периферийных устройствах (например, регистры состояния) (b) Глобальные переменные, используемые в обработчиках прерываний. (с) Глобальные переменные, используемые совместно несколькими задачами в многопотоковом приложении.
Если кандидат не знает ответ на этот вопрос, он не получит работу. Я считаю, что это наиболее существенный вопрос, который позволяет отличить «Си-программиста» от «программиста встраиваемых систем». Программисты встраиваемых систем сталкиваются с аппаратными средствами, прерываниями, ОСРВ, и тому подобным. Все эти вещи требуют использования volatile переменных. Непонимание идеи спецификатора volatile приведет к катастрофе.
Исходя из (сомнительного) предположения, что интервьюируемый ответит на этот вопрос правильно, я люблю копнуть немного глубже, чтобы посмотреть, на самом ли деле он полностью понимает значение этого спецификатора. В частности, я задам ему следующие вопросы:
(а) Может ли аргумент быть одновременно и const и volatile? Аргументируйте ваш ответ. (b) Может ли указатель быть volatile? Аргументируйте ваш ответ. (с) Что не так со следующей функцией?:
int square(volatile int *ptr) { return *ptr * *ptr; }
Ответы следующие:
(а) Да. Например, регистр состояния доступный только для чтения. Он volatile, потому что может меняться неожиданно. Он const, потому что программа не должна пытаться изменить его. (b) Да. Хотя это не общепринятый случай. Например, когда обработчик прерываний изменяет указатель на буфер. (с) Эта функция потенциально опасна. Назначение кода состоит в возвращении квадрата значения, указанного при помощи *ptr. Однако, поскольку *ptr указывает на volatile переменную, компилятор сгенерирует код, который выглядит примерно так:
int square(volatile int *ptr) { int a,b; a = *ptr; b = *ptr; return a * b; }
Поскольку значение переменной на которую указывает ptr может неожиданно измениться, то существует возможность, что «а» и «b» будут разными. Следовательно, этот код может возвратить число, которое не будет квадратом! Правильный вариант кода в данном случае такой:
int square(volatile int *ptr) { int a; a = *ptr; return a * a; }
При программировании встраиваемых систем приходится часто манипулировать битами в регистрах или переменных. Дана целая переменная «а», напишите два фрагмента кода. Первый должен установить 3-ий бит этой переменной. Второй должен очищать его. В обоих случаях, другие биты должны остаться без изменений.
Вот три наиболее распространённых ответа на этот вопрос:
(а) Никаких идей. Кандидат никоим образом не может работать со встраиваемыми системами. (b) Используйте битовые поля. Битовые поля покинули этот мир, наряду с триграфами, как самая бестолковая часть языка Си. Битовые поля по своей природе не переносимы между компиляторами, и по существу гарантируют, что ваш код не допускает многократного использования. Недавно я имел несчастье взглянуть на драйвер, который написали в Infineon для одного из их наиболее сложных коммуникационных чипов. Там использовались битовые поля, и драйвер был полностью бесполезен, потому что мой компилятор выполнял битовые поля другим путём. Мораль – никогда не позволяйте программисту, не знакомому со встраиваемыми системами, подходить к реальному оборудованию. Я недавно смягчил свою позицию по битовым полям. По меньшей мере один производитель компиляторов (IAR) теперь предлагает ключ компилятора для определения расположения битовых полей. К тому же их компилятор генерирует оптимальный код с регистрами описанными как битовые поля, и по существу - теперь я использую битовые поля в IAR-приложениях. (с) Используйте #define и битовые маски. Это хорошо переносимый метод и его стоит использовать. Оптимальное решение этой проблемы, на мой взгляд, было бы таким:
#define BIT3 (0?1 << 3)
static int a;
void set_bit3(void) { a |= BIT3; }
void clear_bit3(void) { a &= ~BIT3; }
Некоторые люди, наряду с именованными константами, предпочитают задавать маску для значений set и clear. Это тоже приемлемо. Важные элементы, которые мне нужны – это использование именованных констант, наряду с конструкциями |= и &= ~
Довольно часто программистам встраиваемых систем требуется доступ к определенной ячейке памяти. В некотором проекте требуется установить целую переменную по абсолютному адресу 0?67a9 к значению 0xaa55. Напишите код, выполняющий эту задачу.
Цель данной задачи выяснить, знаете ли Вы, что разрешено приведение целочисленных типов к указателю, чтобы получить доступ по абсолютному адресу. Правильный синтаксис может варьироваться в зависимости от стиля. Однако обычно я хочу увидеть что-то вроде этого:
int *ptr; ptr = (int *)0?67a9; *ptr = 0xaa55;
Более запутывающий вариант выглядит так:
*(int * const)(0?67a9) = 0xaa55;
Прерывания являются важной частью встраиваемых систем. Поэтому многие производители компиляторов предлагают к стандартному языку Си расширение для поддержки прерываний. Обычно - это новое ключевое слово __interrupt. Следующий код использует __interrupt, чтобы описать программу обработки прерываний. Прокомментируйте его.
__interrupt double compute_area(double radius) { double area = PI * radius * radius; printf(“\nArea = %f”, area); return area; }
В этой функции так много неправильного, что почти невозможно понять с чего начинать.
(а) Обработчик прерываний не может возвращать значение. Если вы не понимаете этого, то не получите работу. (b) Обработчик прерываний не может принимать параметры. Если вы упустили это, смотрите пункт (а) для понимания ваших перспектив получить работу. (с) Во многих процессорах/компиляторах, операции с плавающей точкой не обязательно реентерабельны. В некоторых случаях они нуждаются в добавлении в стек дополнительных регистров, в других случаях, они просто не могут выполнить эти операции в обработчике прерываний. Более того, при условии главного практического требования, что обработчики прерываний должны быть короткими и ясными, возникает вопрос об уместности выполнения здесь подобной математики. (d) Функция printf() тоже часто испытывает трудности с реентерабельностью и работоспособностью. Если бы вы пропустили пункты (с) и (d), то я не был бы с вами слишком строг. А если вы справились с этим двумя пунктами, то ваши перспективы получить работу выглядят всё лучше и лучше.
Что делает следующий фрагмент кода и почему?
void foo(void) { unsigned int a = 6; int b = - 20;
(a + b > 6) ? puts("> 6") : puts("<= 6"); }
Этот вопрос проверяет, разбираетесь ли вы в правилах представления целых чисел в Си, – области, которую я нахожу очень плохо понимаемой многими разработчиками. Ответ состоит в том, что выведется "> 6". Причина - в выражениях, включающих типы со знаком и без все операнды приводятся к типам без знака. Таким образом, -20 становится очень большим положительным целым, и выражение оценивается больше, чем 6. Это очень важный момент во встраиваемых системах, где часто используются типы данных без знака. Если вы ответили на этот вопрос неправильно, то вы сильно рискуете быть не принятым на работу.
Прокомментируйте следующий фрагмент кода:
unsigned int zero = 0; unsigned int compzero = 0xFFFF; /*1’s complement of zero */
В машинах, где int не равен 16-и битам, это будет неправильно. Следует записать так:
unsigned int compzero = ~0;
Этот вопрос даёт возможность по-настоящему узнать, понимает ли кандидат важность длины слова в компьютере. На этой стадии кандидаты либо полностью деморализованы, либо они на коне и предаются приятному времяпровождению. Если очевидно, что кандидат не очень хорош, то на этом тест заканчивается. Однако если кандидат отлично себя проявил, то я задаю дополнительные вопросы. Эти вопросы сложные и я ожидаю, что только лучшие кандидаты справятся с ними.
В чём заключаются проблемы с динамическим распределением памяти во встраиваемых системах?
Здесь я ожидаю, что пользователь упомянет фрагментацию памяти, проблемы сбора мусора и т.д. Эта тема была хорошо описана в ESP, главным образом, Плогером. Его объяснения гораздо более проницательны, чем что-либо из того, что я могу предложить здесь, поэтому прочтите старые номера! Убаюкав кандидата ощущением ложной безопасности, я задаю такой интересный вопрос:
Что делает следующий фрагмент кода и почему?
char *ptr; if ((ptr = (char *)malloc(0)) == NULL) { puts(“Got a null pointer”); } else { puts(“Got a valid pointer”); }
Это довольно забавная задачка. Я столкнулся с ней недавно, когда мой коллега ненароком передал значение 0 в malloc и получил назад действительный указатель! Покопавшись немного, я обнаружил, что результат функции malloc (0) определён реализацией, так что правильный ответ гласит «по обстоятельствам». Я использую этот вопрос, чтобы начать беседу о том, что, по мнению интервьюируемого, является верным для действий функции malloc. Получение правильного ответа здесь не важно, главное как вы подойдёте к проблеме и логически обоснуете свое решение.
Typedef часто используется в Си для объявления синонимов существующих типов данных. Также для подобных действий возможно использование препроцессора. Например, рассмотрите следующий фрагмент кода:
#define dPS struct s * typedef struct s * tPS;
Цель в обоих случаях состоит в объявлении dPS и tPS как указателей на структуру «s». Какой метод предпочтительней и почему?
Это очень хитрый вопрос, и всякий, кто ответит на него правильно, получит мои совершенно справедливые поздравления. Ответ заключается в том, что предпочтителен typedef. Рассмотрите объявления:
dPS p1,p2; tPS p3,p4;
Из первого примера следует:
struct s * p1, p2;
что объявляет р1 как указатель на структуру и р2 как обычную структуру, что, вероятно, вовсе не то, что вы хотели. Второй пример правильно определяет р3 и р4 как указатели.
Си позволяет некоторые ужасные конструкции. Допустима ли эта конструкция, и если да, то что делает этот код?
int a = 5, b = 7, c;
c = a+++b;
Предполагается, что этот вопрос будет весёлым завершением опроса, в то время как, верите, или нет, это вполне допустимый синтаксис. Вопрос в том, как компилятор его воспримет? Бедные авторы компиляторов обсуждали эту проблему и пришли к правилу «максимального перемалывания», которое гласит, что компилятор должен «откусить» такой большой (допустимый) кусок, какой только может. Следовательно, этот код будет восприниматься так:
c = a++ + b;
Поэтому, после выполнения этого кода, a = 6, b = 7 & c = 12;
Если вы знали ответ, или правильно угадали – тогда дело сделано. Если вы не знали ответа – не страшно. Я нахожу наибольшую пользу от этой задачи в том, что она очень хорошо стимулирует вопросы относительно стилей кодирования и преимуществ использования контроля стиля программирования на соответствие стандартам.
Размеры типов (g++ 4.7.2) #include
int main()
{
std::cout << "void * = " << sizeof( void * ) << std::endl;
std::cout << "bool = " << sizeof( bool ) << std::endl;
std::cout << "signed char = " << sizeof( signed char ) << std::endl;
std::cout << "unsigned char = " << sizeof( unsigned char ) << std::endl;
std::cout << "signed short int = " << sizeof( signed short int ) << std::endl;
std::cout << "unsigned short int = " << sizeof( unsigned short int ) << std::endl;
std::cout << "signed int = " << sizeof( signed int ) << std::endl;
std::cout << "unsigned int = " << sizeof( unsigned int ) << std::endl;
std::cout << "signed long int = " << sizeof( signed long int ) << std::endl;
std::cout << "unsigned long int = " << sizeof( unsigned long int ) << std::endl;
std::cout << "signed long long int = " << sizeof( signed long long int ) << std::endl;
std::cout << "unsigned long long int = " << sizeof( unsigned long long int ) << std::endl;
std::cout << "float = " << sizeof( float ) << std::endl;
std::cout << "double = " << sizeof( double ) << std::endl;
std::cout << "long double = " << sizeof( long double ) << std::endl;
return 0;
}
Answer: void * = 8 bool = 1 signed char = 1 unsigned char = 1 signed short int = 2 unsigned short int = 2 signed int = 4 unsigned int = 4 signed long int = 8 unsigned long int = 8 signed long long int = 8 unsigned long long int = 8 float = 4 double = 8 long double = 16
Deadlock
A deadlock is a situation in which two or more competing actions are each waiting for the other to finish, and thus neither ever does.
Такая ситуация когда два потока ждут друг друга и называется Deadlock
Deadlock — ситуация в многозадачной среде или СУБД, при которой несколько процессов находятся в состоянии бесконечного ожидания ресурсов, захваченных самими этими процессами.
Maybe a simple bank situation.
class Account {
private:
double balance;
int id;
public:
void withdraw(double amount) {
balance -= amount;
}
void deposit(double amount) {
balance += amount;
}
void transfer(Account from, Account to, double amount) {
sync(from);
sync(to);
from.withdraw(amount);
to.deposit(amount);
release(to);
release(from);
}
}
Obviously, should there be two threads which attempt to run transfer(a,b) and transfer(b,a) at the same time, then a deadlock is going to occur.
This code is also great for looking at solutions to the deadlock as well.