Лекция
Сразу хочу сказать, что здесь никакой воды про исключительные ситуаций, и только нужная информация. Для того чтобы лучше понимать что такое исключительные ситуаций, try , настоятельно рекомендую прочитать все из категории Объектно-ориентированное программирование ООП.
Шаблоны, обобщенное программирование, библиотеки шаблонов . Родовые функции, родовые классы. Шаблон функции и классы, структура, правила конструирования. Шаблонный класс stack. : друзья, статические члены, аргументы шаблона класса
Шаблон класса Vector . Структура шаблона, члены класса, итераторы, конструкторы, способы, аргументы, примеры использования.
Шаблон класса list . Структура шаблона, члены класса, итераторы, конструкторы, способы, аргументы, примеры использования.
Шаблон basic_string. Структура шаблона, члены класса string , итераторы, конструкторы, методы, аргументы, примеры использования.
Контейнеры . Основные понятия и определения. Последовательные контейнеры, примеры программ. Ассоциативные контейнеры, примеры программ. Адаптеры контейнеров, примеры программ.
Шаблон класса map. Структура шаблона, члены класса, итераторы, конструкторы, способы, аргументы, примеры использования. Очередь с двумя концами, с приоритетом, поиск, хеширование, удаление и повторное хеширование.
Шаблон класса algoritm. Структура шаблона, члены класса, итераторы, конструкторы, способы, аргументы, примеры использования. Алгоритмы сортировки.
Особенности обработки исключительных ситуаций. Общие принципы механизма обработки исключительных ситуаций. Синтаксис и семантика по генерации и обработке исключений. Обработка исключительных ситуаций при динамичном выделении памяти. Функции, глобальные переменные и классы поддержки механизма исключений.
Механизм шаблонов встроен в компилятор C++, чтобы дать возможность программистам делать свой код короче за счет обобщенного программирования. Естественно, существуют и стандартные библиотеки, реализующие этот механизм. STL является самой эффективной библиотекой C++ на сегодняшний день.
Сейчас существует немало ее реализаций, каждая из которых, хоть и создана в рамках стандарта, обладает собственными расширениями. У подобного подхода есть один недостаток: не всегда код будет работать одинаково с разными компиляторами. Поэтому настоятельно рекомендуем вам максимально придерживаться традиционных приемов, как бы хорошо вы не разбирались в конкретной реализации библиотеки.
Для начала рассмотрим самые популярные коллекции из библиотеки. Каждая из них имеет собственный набор шаблонных параметров, чтобы быть максимально удобной для как можно большего спектра решаемых задач.
Для использования коллекции в своем коде используйте следующую директиву:
#include ,
где T — название коллекции
Итак, наиболее часто используются:
Любая серьезная библиотека имеет свои классы для представления строк. В STL строки представляются как в формате ASCII, так и Unicode:
string — коллекция однобайтных символов в формате ASCII;
wstring — коллекция двухбайтных символов в формате Unicode; включается командой #include .
strstream — используются для организации STL-строкового сохранения простых типов данных.
Разбор примеров начнем именно с этого класса.
//stl.cpp: Defines the entry point for the console application #include "stdafx.h" #include #include #include using namespace std; int _tmain (int argc, _TCHAR* argv []) { strstream xstr; for (int i = 0; i < 10; i++) { xstr << "Demo " << i << endl; } cout << xstr.str (); string str; str.assign (xstr.str (), xstr.pcount ()); cout << str.c_str (); return 0; }
Строковый поток — это буфер с нуль-терминатором в конце, поэтому при первой распечатке в конце строки оказывается мусор, т.е. получить реальный конец можно не посредством нуль-терминатора, а получив счетчик: pcount(). Затем “реальная часть” потока копируется в новую строку, и мы получаем распечатку уже без мусора.
Очень важное понятие в реализации динамических структур данных — итератор. Неформально итератор можно определить как абстракцию, которая ведет себя как указатель, возможно, с какими-то ограничениями. Строго говоря, итератор — более общее понятие, и является объектной оберткой для указателя, поэтому указатель является итератором. Примерно его устройство может выглядеть так:
class Iterator { T* pointer; public: T* GetPointer () { return this - >pointer; } void SetPointer (T* pointer) { this - >pointer = pointer; } };
Вот несколько формализованных определений итератора:
Существуют три типа итераторов:
Вот пример использования итераторов для удаления половин элементов коллекции:
#include "stdafx.h" #include #include #include using namespace std; void printInt (int number); int _tmain (int argc, _TCHAR* argv []) { vector myVec; vector::iterator first, last; for (long i=0; i<10; i++) { myVec.push_back (i); } first = myVec.begin (); last = myVec.begin () + 5; if (last >= myVec.end ()) { return - 1; } myVec.erase (first, last); for_each (myVec.begin (), myVec.end (), printInt); return 0; } void printInt (int number) { cout << number << endl; }
Важно понимать, что при получении итератора на какой-то элемент коллекции и последующем изменении коллекции итератор может стать непригоден для использования.
Итерация вперед и аналогично назад происходит так:
for (iterator element = begin (); element < end (); element++) { t = (*element); }
При использовании random access iterator, например, так:
for (iterator element = begin (); element < end (); element+=2) { t = (*element);}
Основными методами, присутствующими почти во всех коллекциях являются следующие:
Вместимость коллекции, как было сказано в начале, меняется по мере надобности, т.е. если вся выделенная под коллекцию память уже заполнена, то при добавлении нового элемента вместимость коллекции будет увеличена, а все значения, бывшие в ней до увеличения, будут скопированы в новую область памяти — это довольно “дорогая” операция. Убедиться в том, что размер и вместимость — разные вещи, можно на следующем примере:
vector vec; cout << "Real size of array in vector: " << vec.capacity () << endl; for (int j = 0; j < 10; j++) { vec.push_back (10); } cout << "Real size of array in vector: " << vec.capacity () << endl; return 0;
Самая часто используемая коллекция — это вектор. Очень удобно, что у этой коллекции есть такой же оператор operator [], что и у обычного массива. Такой же оператор есть и у коллекций map, deque, string и wstring.
Важно понимать, что вместимость vector'а изменяется динамически. Обычно для увеличения размера используется мультипликативный подход: выделенная под vector память увеличивается при необходимости в константное число раз, т.е. если добавление нового элемента приведет к тому, что размер массива превысит вместимость, то операционной системой для программы будет выделен новый участок памяти, например, в два раза больший, в который будут скопированы все значения из старого участка памяти и к которому будет дописано новое значение.
Разработчики библиотеки STL ставили перед собой гораздо более серьезную задачу, чем создание библиотеки с набором шаблонных структур данных. STL содержит огромный набор оптимальных реализаций популярных алгоритмов, позволяющих работать с STL-коллекциями. Все реализованные функции можно поделить на три группы:
Задачей этой статьи является лишь познакомить читателя с богатым набором инструментов, предоставляемых библиотекой STL. Более подробную информацию можно узнать из соответствующей документации.
Для многих алгоритмов STL можно задать условие, посредством которого алгоритм определит, что ему делать с тем или иным членом коллекции. Предикат — это функция, которая принимает несколько параметров и возвращает логическое значение (истина/ложь). Существует и набор стандартных предикатов.
Важно понимать, что STL — не потокобезопасная библиотека. Но решить эту проблему очень просто: если два потока используют одну коллекцию, просто реализуйте критическую секцию и Mutex.
STL — кросс-платформенная библиотека. Конечно, не существует абсолютной гарантии, что эта библиотека есть в любой версии компилятора. Например, она редко реализуется на мобильных устройствах, потому что большая часть реализованных структур данных делает выбор в пользу быстродействия, совершенно не экономя память, а ведь именно память является самым ценным ресурсом на мобильных платформах, в то время как на PC ее сейчас в избытке. Поэтому, нередко вам придется создавать свои реализации STL, например, для переноса вашего приложения на мобильную платформу.
Обработка исключений (exception handling) позволяет упорядочить обработку ошибок времени исполнения. Используя обработку исключений С++, программа может автоматически вызвать функцию-обработчик ошибок тогда, когда такая ошибка возникает. Принципиальным достоинством обработки исключений служит то, что она позволяет автоматизировать большую часть кода для обработки ошибок, для чего раньше требовалось ручное кодирование.
Обработка исключений в С++ использует три ключевых слова: try, catch и throw. Те инструкции программы, где ожидается возможность появления исключительных ситуаций, содержатся в блоке try. Если в блоке try возникает исключение, т. е. ошибка, то генерируется исключение. Исключение перехватывается, используя catch, и обрабатывается. Ниже это общее описание будет рассмотрено более подробно.
Инструкция, генерирующая исключение, должна исполняться внутри блока try. Вызванные из блока try функции также могут генерировать исключения. Всякое исключение должно быть перехвачено инструкцией catch, которая непосредственно следует за инструкцией try, сгенерировавшей исключение. Общая форма блоков try и catch показана ниже:
try {
// блок try
catch (тип1 аргумент) {
// блок catch
catch (тип2 аргумент) {
// блок catch
catch (типЗ аргумент) {
// блок catch
}
...
catch (типN аргумент) {
// блок catch
}
Размеры блока try могут изменяться в больших пределах. Например, блок try может содержать несколько инструкций какой-либо функции, либо же, напротив, включать в себя весь код функции main(), так что вся программа будет охвачена обработкой исключений.
Когда исключение сгенерировано, оно перехватывается соответствующей инструкцией catch, обрабатывающей это исключение. Одному блоку try может отвечать несколько инструкций catch, Какая именно инструкция catch исполняется, зависит от типа исключения. Это означает, что если тип данных, указанных в инструкции catch, соответствует типу данных исключения, то только эта инструкция catch и будет исполнена. Когда исключение перехвачено, arg получает ее значение. Перехваченным может быть любой тип данных, включая созданные программистом классы. Если никакого исключения не сгенерировано, то есть никакой ошибки не возникло в блоке try, то инструкции catch выполняться не будут.
Общая форма записи инструкции throw имеет вид:
throw исключение;
Инструкция throw должна выполняться либо внутри блока try, либо в функции, вызванной из блока try. В записанном выше выражении исключение обозначает сгенерированное значение.
Если генерируется исключение, для которого отсутствует подходящая инструкция catch, может произойти аварийное завершение программы. При генерации необработанного исключения
вызывается функция terminate(). По умолчанию terminate() вызывает функцию abort(), завершающую выполнение программы. Однако можно задать свою собственную обработку, используя функцию set_terminate(). Подробности можно найти в документации к компилятору.
Ниже представлен пример, иллюстрирующий способ обработки исключений в С++:
// пример обработки простого исключения
#include
int main()
{
cout << "Start\n";
try { // начало блока try
cout << "Inside try block\n";
throw 100; // генерация ошибки
cout << "This will not execute";
}
catch (int i) { // перехват ошибки
cout << "Caught an exception -- value is: ";
cout << i << " \n";
}
cout << "End";
return 0;
}
Программа выведет на экран следующий текст:
Start
Inside try block
Caught an exception -- value is: 100
End
Рассмотрим внимательнее эту программу. Как можно видеть, блок try содержит три инструкции. За ним следует инструкция catch(int i), обрабатывающая исключения целого типа. В блоке try будут выполняться только две инструкции: первая и вторая — throw. Как только исключение было сгенерировано, управление передается инструкции catch, а блок try прекращает свое исполнение. Таким образом, catch не вызывается. Скорее можно сказать, что к ней переходит исполнение программы. Для этого автоматически осуществляется переустановка стека. Таким образом, инструкция после инструкции throw никогда не выполняется.
Обычно код в инструкции catch пытается исправить ошибку путем выполнения подходящих действий. Если ошибку удалось исправить, то выполнение продолжается с инструкции, непосредственно следующей за catch. Однако иногда не удается справиться с ошибкой, и блок catch завершает программу путем вызова функции exit() или функции abort().
Как отмечалось, тип исключения должен соответствовать указанному в инструкции типу. Например, в предыдущем примере если изменить тип инструкции catch на double, то исключение не будет перехвачено и произойдет аварийное завершение программы. Такое изменение показано ниже:
// данный пример не будет работать
#include
int main()
{
cout << "Start\n";
try { // начало блока try
cout << "Inside try block\n";
throw 100; // генерация ошибки
cout << "This will not execute";
}
catch (double i) { // не будет работать для целочисленного исключения
cout << "Caught an exception -- value is: ";
cout << i << "\n";
}
cout << "End";
return 0;
}
Эта программа выдаст следующий результат, поскольку исключение целого типа не будет перехвачено инструкцией catch (double i):
Start
Inside try block
Abnormal program termination
Исключение может также быть сгенерировано из функции, вызванной изнутри блока try. В качестве примера рассмотрим следующую программу:
/* генерация исключения из функции, находящейся вне блока try
*/
#include
void Xtest(int test)
{
cout << "Inside Xtest, test is: " << test << "\n";
if (test) throw test;
}
int main()
{
cout << "Start\n";
try { // начало блока try
cout << "Inside try block\n";
Xtest (0);
Xtest (1);
Xtest (2);
}
catch (int i) { // перехват ошибки
cout << "Caught an exception -- value is: ";
cout << i << "\n";
}
cout << "End";
return 0;
}
Эта программа выдаст следующий результат:
Start
Inside try block
Inside Xtest, test is: 0
Inside Xtest, test is: 1
Caught an exception -- value is: 1
End
Блок try может быть локализован в какой-то функции. В таком случае всякий раз при входе в функцию начинается обработка исключений. В качестве примера рассмотрим следующую программу:
#include
// try/catch могут находиться в функции вне main()
void Xhandler(int test)
{
try {
if (test) throw test;
}
catch(int i) {
cout << "Caught Exception #: " << i << '\n';
}
}
int main()
{
cout << "Start\n";
Xhandler(1);
Xhandler(2);
Xhandler(0);
Xhandler(3);
cout << "End";
return 0;
}
Эта программа выдаст на экран следующий текст:
Start
Caught Exception #: 1
Caught Exception #: 2
Caught Exception #: 3
End
Как видно, сгенерировано три исключения. После каждого исключения функция возвращает управление в функцию main. При каждом новом вызове функции возвращается обработка исключений.
Важно понимать, что код, ассоциированный с инструкцией catch, будет исполняться только тогда, когда перехвачено исключение. В противном случае выполнение программы просто обойдет инструкцию catch.
А как ты думаешь, при улучшении исключительные ситуаций, будет лучше нам? Надеюсь, что теперь ты понял что такое исключительные ситуаций, try и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Объектно-ориентированное программирование ООП
Комментарии
Оставить комментарий
Объектно-ориентированное программирование ООП
Термины: Объектно-ориентированное программирование ООП