- •Тема 1: Динамическая память и интеллектуальные указатели
- •1. Особенности использования динамической памяти (распределяемая память, heap, freestore), стека. Примеры использования.
- •2. Особенности выделения и освобождения памяти для автоматических глобальных и локальных объектов, статических глобальных и локальных объектов. Примеры использования.
- •3. Операторы new и delete при работе с динамической памятью. Свойства указателей, передаваемых оператору delete. Примеры использования.
- •4. Утечка памяти (memory leak). Потерянный указатель (dangling pointer, wild pointer). Примеры использования.
- •5. Проблемы, связанных с использованием операторов new и delete при управлении динамической памятью. Примеры использования.
- •6. Динамически создаваемые объекты (dynamically allocated objects). Интеллектуальные указатели (smart pointers): преимущества, недостатки, особенности использования, различия. Примеры использования.
- •8. Класс интеллектуального указателя shared_ptr. Методы класса указателя shared_ptr. Особенности и примеры использования.
- •9. Копирование и присваивание указателей shared_ptr. Примеры использования.
- •10. Создание и использование объектов интеллектуальных указателей. Примеры.
- •11. Функция make_shared(). Использование функции make_shared() для создания и инициализации объектов интеллектуальных указателей. Примеры использования.
- •12. Счетчик ссылок (reference count), счетчик слабых ссылок (weak count), функция-удалитель (deleter), выделитель памяти (allocator). Примеры использования.
- •13. Использование ключевого слова auto. Примеры использования.
- •14. Классы, ресурсы которых имеют динамическую продолжительность существования. Случаи их использования. Совместное использование данных двумя объектами. Примеры использования.
- •15. Использование оператора new для динамического резервирования памяти и инициализации объектов. Примеры использования.
- •17. Особенности создание пользовательского класса умного указателя (smart pointer). Перечень необходимых условий для реализации пользовательского класса умного указателя. Пример использования.
- •18. Особенности копирования, присваивания и удаления объектов при создании пользовательского класса умного указателя (smart pointer). Пример использования.
- •19. Размещающий оператор new (placement new). Передача ему объекта nothrow. Пример использования.
- •20. Особенности исчерпания памяти. Исключения, возникающие при исчерпании памяти. Пример использования.
- •21. Время жизни динамически создаваемых объектов. Примеры использования.
- •22. Присваивание указателю значения nullptr. Примеры использования.
- •23. Использование указателя shared_ptr совместно с оператором new. Примеры использования.
- •24. Особенности совместного использования обычных указателей и интеллектуальных. Примеры использования.
- •30. Передача в функцию и возврат из функции указателя типа unique_ptr. Примеры использования.
- •31. Использование класса auto_ptr. Особенности и пример использования.
- •32. Передача функции удаления указателю unique_ptr. Примеры использования.
- •33. Класс интеллектуального указателя weak_ptr. Методы класса указателя weak_ptr. Особенности и пример использования.
- •34. Динамические массивы. Особенности работы с динамическими массивами. Особенности резервирования памяти для массивов. Динамическое резервирование пустого массива. Примеры использования
- •35. Оператор new и динамические массивы. Класс bad_array_new_length. Примеры использования.
- •36. Инициализация массива динамически созданных объектов встроенных и пользовательских типов. Примеры использования.
- •37. Особенности освобождение памяти, выделенной для динамических массивов. Примеры использования.
- •38. Взаимодействие интеллектуальных указателей (класс unique_ptr) и динамических массивов. Примеры использования.
- •39. Класс Allocator и специальные алгоритмы. Примеры использования.
- •40. Алгоритмы копирования и заполнения неинициализированной памяти. Примеры использования
- •1. Особенности обработки исключительных ситуаций с использованием функции abort() и exit(). Преимущества и недостатки. Примеры использования.
- •2. Особенности обработки исключительных ситуаций с помощью возврата кода ошибки. Преимущества и недостатки. Примеры использования.
- •4. Роль типа исключения в его обработке. Охранный блок, блок перехвата. Использование механизма обработки исключений. Примеры использования.
- •5. Использование объектов классов в качестве исключений. Одинаковые свойства и различия операторов throw и return. Примеры использования.
- •7. Алгоритм раскручивания стека. Раскручивание стека при нормальном и аварийном завершении программы. Пример использования.
- •8. Понятие абсолютного обработчика, его синтаксис, особенности использования. Пример использования.
- •9. Класс exception. Методы класса exception. Использование наследования класса exception. Пример использования.
- •11. Исключение bad_alloc и операция new. Примеры использования. Использование нулевого указателя и операции new. Примеры использования.
- •12. Использование исключений вместе с классами и наследованием. Особенности использования вложенных классов.
- •13. Потеря исключений. Неперехваченное исключение. Использование функций terminate() и set_terminate().
- •14. Непредвиденное исключение. Использование функций unexpected() и set_unexpected(). Исключение типа std::bad_exception.
- •15. Предостережения относительно использования исключений. Использование и управление исключениями в современных языках программирования.
- •4. Изменение основания системы счисления, используемого для отображения данных. Набор констант типа fmtflags (константы форматирования), решаемые ими задачи.
- •7. Istream как псевдоним шаблона класса basic_istream. Взаимосвязь основных классов ввода/вывода. Ввод данных с помощью объекта cin
- •Istream и ostream как специализации для специализаций char. Wistream и wostream как специализации для типа wchar_t.
- •10. Класс basic_iostream. Iostream как псевдоним шаблона класса basic_iostream. Взаимосвязь основных классов ввода/вывода
- •12. Объекты wcout, wcin, wclog, wcerr, cout, cin, clog, cerr. Особенности их создания и использования.
- •14. Заголовочный файл iomanip. Функции setprecision(), setfill(), setw(). Использование манипуляторов из файла iomanip.
- •Тема 4: Файловый ввод-вывод
- •Понятие файла. Бинарные и текстовые файлы. Преимущества, недостатки, особенности использования.
- •Аргументы командной строки (argc, argv). Обработка данных командной строки. Примеры их использования.
- •Проверка состояния файлового потока. Метод is_open(). Особенности открытия нескольких файлов. Пример их использования.
- •Константы типа seekdir: ios_base::beg, ios_base::cur, ios_base::end. Примеры их использования.
- •Особенности работы с временными файлами. Функция tmpnam_s(). Пример её использования.
- •Класс string. Внутреннее форматирование с использованием классов ostringstream, istringstream. Примеры их использования.
- •Тема 5: Стандартная библиотека шаблонов stl.
- •1. Базовые принципы библиотеки шаблонов stl. Использование библиотеки stl. Заголовочные файлы complex, random.
- •4. Иерархия и концепция итераторов. Необходимость использования каждого типа итераторов. Указатель как итератор. Применение алгоритмов stl к контейнерам, не относящимся к библиотеке stl.
- •5. Входные, выходные, однонаправленные, двунаправленные итераторы, итераторы произвольного доступа: понятие, требования, особенности использования, направленность.
- •6. Шаблонный класс vector, deque, stack. Особенности, методы, принципы работы, возможности
- •7. Шаблонный класс array, valarray. Особенности, методы, принципы работы, возможности
- •8. Шаблонный класс list, forward_list. Особенности, методы, принципы работы, возможности
- •9. Шаблонный класс queue, priority_queue. Особенности, методы, принципы работы, возможности
- •10. Шаблонные классы set и multiset. Особенности, методы, принципы работы, возможности
- •11. Шаблонные классы map и multimap. Особенности, методы, принципы работы, возможности
- •12. Понятие функциональных объектов (функторов). Концепции функторов: генератор, унарная функция, бинарная функция, предикат, бинарный предикат. Предопределенные функторы
- •14. Группы алгоритмов. Алгоритмы «по месту», копирующие алгоритмы. Сравнение функций stl и методов контейнеров stl
- •15. Математические операции и их эквиваленты-функторы. Понятие полного упорядочения и квазиупорядочения
- •17. Понятие обобщенного программирования. Связь обобщенного программирования и библиотеки stl.
- •18. Использование алгоритма copy(), классов ostream_iterator и istream_iterator в качестве моделей входных и входных итераторов
- •19. Итераторы специального назначения: reverse_iterator, back_insert_iterator, front_insert_iterator, insert_iterator
- •Тема 6: Обзор Java. Введение в ооп в Java.
- •2. Обзор и отличительные особенности языка Java. Программная платформа и виртуальная машина Java. Особенности разработки и исполнения объектно-ориентированных приложений на Java.
- •3. Сборка мусора в Java. Пакет jdk: особенности, содержимое, необходимость использования, версии. Ide для работы на Java.
- •4. Особенности настройки работы платформы Java и запуск приложения на языке Java без ide.
- •5. Особенности лексики Java: литералы, идентификаторы, разделители, комментарии, ключевые слова.
- •6. Примитивные типы данных Java. Типизация. Целые числа (byte, short, int, long), числа с плавающей точкой (float, double), символы.
- •7. Переменные. Объявление переменной. Преобразование и приведение типов. Автоматическое приведение и продвижение типов в выражениях. Логические выражения. Область и срок действия переменной.
- •8. Операции (арифметические, поразрядные, отношения, логические (укороченные, обычные)). Операция присваивания. Предшествование операций.
- •9. Управляющие операторы (выбора, цикла, перехода). Разновидность цикла for в стиле for each. Комментарии в Java. Оператор instanceof.
- •Принципы объектно-ориентированного программирования в Java.
15. Предостережения относительно использования исключений. Использование и управление исключениями в современных языках программирования.
Обработка исключений должна быть встроена в программу, а не присоединена извне. Однако у такого подхода есть свои недостатки. Например, исключения увеличивают размер программы и снижают скорость ее выполнения. Спецификации исключений плохо сочетаются с шаблонами, т.к. шаблонные функции могут генерировать различные виды исключений, в зависимости от конкретной специализации. Динамическое распределение памяти также не всегда гладко взаимодействует с исключениями.
Рассмотрим немного подробнее совместную работу динамического распределения памяти и исключений. Пусть имеется следующая функция:
void testl(int n){ string mesg("I'm trapped in an endless loop"); if(oh_no) throw exception();
//...
return;
}
Класс string использует динамическое распределение памяти. Обычно деструктор string вызывается, когда функция доходит до оператора return и завершается. Благодаря раскручиванию стека оператор throw позволяет вызвать деструктор, хотя он и завершает функцию преждевременно. То есть в этом случае управление памятью выполняется без проблем.
Теперь рассмотрим следующую функцию:
void test2 (int n){ double* ar = new double [n]; if (oh_no) throw exception();
delete [] ar; return;
}
А вот здесь проблема присутствует. Раскручивание стека удаляет переменную ar из стека. Однако из-за преждевременного завершения функции оператор delete [] в конце тела функции будет пропущен. То есть указатель уничтожен, а память, на которую он указывал, остается выделенной, хотя обратиться к ней невозможно. В данном случае имеется утечка памяти.
Такую утечку можно устранить. Например, можно перехватить исключение в той же функции, которая его сгенерировала, добавить в блок catch код очистки и сгенерировать исключение заново:
void test3(int n){ double * ar = new double [n]; try { if (oh_no) throw exception();
}
catch(exception & ex){ delete [] ar; throw;
//... delete [] ar; return;
}
Однако такой подход при недостаточной внимательности может породить новые ошибки. Другой способ основан на использовании шаблонов интеллектуальных указателей.
В общем, несмотря на исключительную важность управления исключениями в некоторых проектах, оно требует дополнительных усилий по программированию, увеличивает размер программы и замедляет ее работу. Правда, ущерб от отсутствия таких проверок может быть гораздо большим.
В современных библиотеках управление исключениями может достичь новых уровней сложности — в основном из-за недокументированных или плохо документированных подпрограмм обработки исключений. Каждый, кто знаком с современными операционными системами, наверняка сталкивался с ошибками и проблемами, которые связаны с необработанными исключениями.
При этом программистам часто приходится до изнеможения копаться в содержимом библиотек, разбираясь, какие исключения сгенерированы, когда и где они возникли и как их обработать.
Освоение обработки ошибок и исключений на основе документации и кода библиотеки принесет программам большую пользу.
Тема 3: Потоки ввода-вывода в языка С++
1. Особенности реализации ввода и вывода в C++. Стандартный пакет ввода-вывода. Заголовочные файлы iostream, fstream. Особенности их подключения и использования. Этапы управления вводом/выводом данных. Заголовок ios: типы и функции для работы с потоками ввода/вывода.
В большинстве языков программирования операции ввода и вывода встроены в сам язык.
Но ни С, ни C++ не имеют операций ввода и вывода, встроенных в сам язык.
Для организации ввода-вывода C++ полагается на решения C++ вместо решений, предлагаемых С, и это решение представляет собой набор классов, определенных в заголовочных файлах iostream (бывший iostream.h) и fstream (бывший fstream.h).
Операция извлечения из потока >> является методом класса istream, операция вставки в поток << — методом класса ostream. Оба этих класса являются наследниками класса ios.
Классы, используемые для вывода данных на экран и ввода с клавиатуры, описаны в заголовочном файле iostream. При подключении этого файла с помощью директивы #include <iostream> в программе автоматически создаются виртуальные каналы связи cin для ввода с клавиатуры и cout для вывода на экран.
Классы, используемые для ввода/вывода файлов, объявлены в файле fstream.
Некоторые заголовки и классы, используемые для реализации ввода/вывода:
Заголовок ios определяет несколько основных типов и функций для работы с потоками ввода/вывода. В заголовке ios определено три класса для реализации основных задач ввода/вывода: ios_base, basic_ios, fpos.
Класс ios_base описывает особенности ввода/вывода общие как для входных, так и для выходных потоков, которые не зависят от параметров шаблона.
Класс basic_ios является шаблонным и описывает свойства общие как для входных потоков (шаблона класса basic_istream), так и для выходных потоков (шаблона класса basic_ostream), которые зависят от параметров шаблона.
Класс basic_istream находится в заголовочном файле <istream> и описывает объекты, управляющие извлечением элементов из буфера потока.
Класс basic_ostream находится в заголовочном файле <ostream> описывает объекты, управляющие вставкой элементов в буфер потока.
Класс ostream является производным для класса basic_ios и предоставляет методы для реализации вывода. Класс ostream представляет собой реализацию шаблонного класса basic_ostream для типа char и char_traits, специализирующийся на char. Класс ostream представляет собой псевдоним шаблона класса basic_ostream, специализированного для элементов типа char с чертами символов по умолчанию.
Класс istream является производным от basic_ios и предоставляет методы ввода из потока. Класс istream представляет собой реализацию шаблонного класса basic_istream для типа char и char_traits, специализирующийся на char. Класс istream представляет собой псевдоним шаблона класса basic_istream, специализированного для элементов типа char с чертами символов по умолчанию.
Класс iostream является производным от классов basic_istream и basic_ostream одновременно и предоставляет методы ввода из потока и вывода в поток. Класс iostream представляет собой реализацию шаблонного класса basic_iostream, который управляет вставками через свой базовый класс basic_ostream и извлечениями из потока через свой базовый класс basic_istream. Оба класса basic_iostream и basic_ostream имеют общий базовый класс basic_ios, который наследуется виртуально.
Заголовок streambuf используется для определения шаблона класса basic_streambuf, предназначенного для получения буфера потока, который управляет передачей элементов в поток и из него.
Класс streambuf является производным от классов basic_ streambuf представляет собой специализацию класса basic_streambuf, которая использует char в качестве параметров шаблона. Класс streambuf является синонимом шаблона класса basic_streambuf, специализированного для элементов типа char с чертами символов по умолчанию.
2. Особенности использования потоков и буферов при вводе/выводе данных. Буфер: понятие, необходимость использования. Буферизация: понятие, преимущества, недостатки. Буферизированный и небуферизированный ввод/вывод: понятие, преимущества, недостатки.
Программа на языке C++ воспринимает ввод и вывод как потоки байтов. При вводе программа извлекает байты из входного потока, а при выводе помещает байты в выходной поток.
Для текстовых программ каждый байт может представлять символ. В общем случае байты могут образовывать двоичное представление символов и числовых данных. Байты входного потока могут поступать с клавиатуры, но также из устройств хранения: жесткого диска, другой программы, сокета. Аналогично, байты выходного потока могут передаваться на дисплей, на принтер, на устройство хранения или же отправляться другой программе.
Потоки служат посредниками между программой и источником или местом назначения потока. Такой подход позволяет программам на C++ трактовать ввод с клавиатуры так же, как и ввод из файла; программа на C++ просто просматривает поток байтов, не нуждаясь в информации о том, откуда эти байты поступают. Точно так же, используя потоки, программа на C++ может обрабатывать вывод независимо от того, куда направляются байты. Таким образом, управление вводом включает две стадии:
ассоциирование потока с вводом программы;
подключение потока к файлу.
Другими словами, входной поток нуждается в двух подключениях — по одному на каждой стороне. Подключение со стороны файла представляет собой источник данных для потока, а подключение со стороны программы загружает выходные данные потока в программу. Подключение со стороны файла может быть файлом, но также оно может быть устройством, таким как клавиатура. Аналогично, управление выводом предусматривает подключение выходного потока к программе и ассоциирование некоторого выходного места назначения с этим потоком. Этот процесс можно сравнить с трубопроводом, по которому вместо воды передаются байты.
Обычно ввод и вывод может более эффективно обрабатываться с использованием буфера. Буфер— это блок памяти, используемый в качестве промежуточного временного хранилища при передаче информации от устройства в программу или из программы устройству.
Обычно устройства передают информацию блоками размером по 512 байт или более, в то время как программы часто обрабатывают данные по одному байту за раз. Буфер облегчает согласование этих двух различных скоростей передачи информации. Например, предположим, что программа должна подсчитать количество символов доллара в файле на жестком диске. Программа может прочитать один символ из файла, обработать его, прочитать следующий символ из файла и т.д. Чтение из дискового файла по одному символу требует множества операций обращения к оборудованию и выполняется медленно.
Буферизованный подход заключается в чтении большой порции данных с диска, сохранении этой порции в буфере и последующем считывании из буфера по одному символу. Поскольку чтение отдельных байтов из оперативной памяти выполняется намного быстрее, чем с жесткого диска, такой подход значительно быстрее и оптимальнее. Конечно, после того, как программа достигает конца буфера, ей приходится затем считывать следующую порцию данных с диска. Принцип подобен использованию резервуара с водой, накапливающего большой объем дождевой воды во время непогоды, из которого затем вода поступает в дом небольшими порциями по мере необходимости.
Клавиатурный ввод поставляет символы по одному, поэтому в его случае программа не нуждается в буфере для согласования разных скоростей передачи данных. Однако буферизованный клавиатурный ввод обеспечивает пользователю возможность вернуться назад и исправить ввод до того, как он будет передан в программу. Обычно программа C++ очищает буфер ввода при нажатии клавиши <Enter>.
Для вывода данных на дисплей программа C++ обычно очищает выходной буфер при передаче символа новой строки. В зависимости от реализации, программа может очищать ввод и в других случаях — например, при предстоящем вводе. То есть, когда программа достигает оператора ввода, она очищает любой вывод, находящийся в выходном буфере.
3. Очистка выходного буфера (автоматическая и принудительная). Манипуляторы fflush и endl. Настройка ширины полей (метод width() и его перегрузки), установка точности отображения чисел с плавающей точкой (метод precision() и его перегрузки). Примеры использования манипуляторов формата. Символы-заполнители. Метод fill(). Пример изменения символазаполнителя полей.
Поскольку буфер класса ostream управляется объектом cout, вывод не отправляется по назначению немедленно. Вместо этого он накапливается в буфере до тех пор, пока не заполнит его. Затем программа очищает буфер, отправляя его содержимое по назначению и освобождая буфер для приема новых данных. Как правило, размер буфера составляет 512 байт или же кратное этому значению число.
Буферизация — способ экономии времени, когда стандартный вывод подключен к файлу на жестком диске. Намного эффективнее, чтобы программа не обращалась к жесткому диску 512 раз для отправки 512 байт, а накапливала эти 512 байт в буфере и затем записывала их на диск в ходе одной операции.
Однако для экранного вывода первоначальное заполнение буфера менее важно. В самом деле, было бы неудобно, если бы пришлось изменять сообщение типа "Нажмите любую клавишу для продолжения" так, чтобы оно заняло все 512 байт, предусмотренные для заполнения буфера. К счастью, в случае экранного вывода программе не обязательно дожидаться заполнения буфера. Например, отправка символа перевода строки обычно ведет к очистке буфера. Кроме того, как упоминалось ранее, большинство реализаций C++ очищают буфер, когда ожидается ввод. Предположим, что имеется следующий код:
cout<< "Enter a number: "; //вывод приглашения на ввод числа float num;
cin >> num;
Тот факт, что программа ожидает ввода, заставляет ее отобразить сообщение cout (т.е. очистить буфер от сообщения "Enter a number: ") немедленно, даже несмотря на то, что строка вывода не содержит символа новой строки. Не будь этой функциональной особенности, программе пришлось бы ожидать ввода, не выдав пользователю приглашения на ввод посредством сообщения cout.
Если используемая реализация не очищает буфер вывода, когда требуется, это можно сделать принудительно с помощью одного из двух манипуляторов. Манипулятор fflush просто очищает буфер, а манипулятор endl очищает буфер и вставляет символ перевода строки. Эти манипуляторы применяются так же, как имена переменных:
cout << "Hello, good-looking! " << fflush;
cout << "Wait just a moment, please." << endl;
Фактически манипуляторы являются функциями. Например, буфер cout можно очистить, вызвав функцию flush() непосредственно:
flush(cout);
Однако класс ostream перегружает операцию << так, что следующее выражение замещается вызовом функции flush (cout):
cout<< flush;
Таким образом, для очистки буфера можно использовать более удобную форму записи операции вставки.
Для размещения чисел различной длины в полях постоянной ширины можно воспользоваться методом-элементом width.
Этот метод имеет следующие прототипы:
Первая форма возвращает текущую установку ширины поля. Вторая устанавливает ширину поля равной _Wide пробелам и возвращает предыдущее значение ширины. Это позволяет сохранить предыдущее значение на случай, если позднее понадобится восстановить старое значение ширины поля.
Метод width() оказывает влияние только на следующий отображаемый элемент, после чего восстанавливается значение поля по умолчанию.
По умолчанию объект cout заполняет неиспользуемые части поля пробелами. Для изменения этого можно воспользоваться функциейэлементом fill(). Например, следующий вызов изменяет символ-заполнитель на звездочку:
cout.fill('*');
Это может быть удобно, например, для распечатки чеков, чтобы получатели не могли добавить к сумме один или два разряда. Применение этой метода-элемента демонстрируется в примере ниже.
//Пример №5. Изменение символа-заполнителя полей
#include <iostream>
int main() {
system("chcp 1251"); system("cls");
using std::cout;
cout.fill('*');
const char* staff[2] = { "Джен Синсеро", "Марк Мэнсон" };
long bonus[2] = { 900, 1350 };
for (int i = 0; i < 2; i++) {
cout << staff[i] << " : $";
cout.width(7);
cout << bonus[i] << "\n";
}
return 0;
}