Обработка исключений в C++

Здравствуйте, молодые программисты! Сегодня мы отправляемся в увлекательное путешествие по миру обработки исключений в C++. В качестве вашего доброго соседа по компьютерной науке, я здесь, чтобы провести вас через эту важную тему. Так что взять свой любимый напиток, устроиться комфортно и погружайтесь вместе с нами!

C++ Exception Handling

Что такое исключения?

Прежде чем мы начнем бросать и ловить исключения, как опытные жонглеры, давайте поймем, что такое исключения. В мире программирования исключения — это непредвиденные события, которые происходят во время выполнения программы. Они похожи на те неожиданные проверочные работы, которые я раньше давал (прости за это!) — неожиданные и иногда несколько сложные для обработки.

Исключения нарушают нормальный поток инструкций программы. Они могут быть вызваны различными факторами, такими как:

  1. Деление на ноль
  2. Доступ к массиву за пределами
  3. Исчерпание памяти
  4. Попытка открыть несуществующий файл

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

Бросание исключений

Основы бросания

В C++ мы используем ключевое слово throw для вызова исключения. Это как поднять руку в классе, когда у вас есть вопрос или проблема. Вот простой пример:

#include <iostream>
using namespace std;

int main() {
try {
throw 20;
}
catch (int e) {
cout << "Произошло исключение. Номер исключения: " << e << endl;
}
return 0;
}

В этом примере мы бросаем целочисленное исключение с значением 20. Но не волнуйтесь, мы поймаем его вот-вот!

Бросание различных типов

C++ гибок и позволяет вам бросать исключения различных типов. Давайте рассмотрим более практичный пример:

#include <iostream>
#include <stdexcept>
using namespace std;

double divide(int a, int b) {
if (b == 0) {
throw runtime_error("Деление на ноль!");
}
return static_cast<double>(a) / b;
}

int main() {
try {
cout << divide(10, 2) << endl;  // Это будет работать без проблем
cout << divide(10, 0) << endl;  // Это вызовет исключение
}
catch (const runtime_error& e) {
cout << "Поймано исключение: " << e.what() << endl;
}
return 0;
}

В этом примере мы бросаем исключение runtime_error, когда кто-то пытается разделить на ноль. Это как установить знак "Нет деления на ноль" в нашем математическом районе!

Поймание исключений

Основы поймания

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

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

#include <iostream>
using namespace std;

int main() {
try {
int age = -5;
if (age < 0) {
throw "Возраст не может быть отрицательным!";
}
cout << "Возраст: " << age << endl;
}
catch (const char* msg) {
cerr << "Ошибка: " << msg << endl;
}
return 0;
}

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

Поймание нескольких исключений

Иногда из одного фрагмента кода могут быть брошены различные типы исключений. В таких случаях у нас может быть несколько блоков catch:

#include <iostream>
#include <stdexcept>
using namespace std;

int main() {
try {
int choice;
cout << "Введите 1 для целочисленного исключения, 2 для ошибки времени выполнения: ";
cin >> choice;

if (choice == 1) {
throw 42;
} else if (choice == 2) {
throw runtime_error("Появилась дикая ошибка времени выполнения!");
} else {
throw "Неизвестный выбор!";
}
}
catch (int e) {
cout << "Поймано целочисленное исключение: " << e << endl;
}
catch (const runtime_error& e) {
cout << "Поймана ошибка времени выполнения: " << e.what() << endl;
}
catch (...) {
cout << "Поймано неизвестное исключение!" << endl;
}
return 0;
}

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

Стандартные исключения C++

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

Вот таблица некоторых часто используемых стандартных исключений:

Исключение Описание
std::runtime_error Логические ошибки времени выполнения
std::logic_error Логические ошибки
std::out_of_range Доступ за пределы диапазона
std::overflow_error Арифметическое переполнение
std::bad_alloc Сбой распределения памяти

Посмотрим пример с использованием стандартного исключения:

#include <iostream>
#include <vector>
#include <stdexcept>
using namespace std;

int main() {
vector<int> numbers = {1, 2, 3, 4, 5};
try {
cout << numbers.at(10) << endl;  // Это вызовет исключение out_of_range
}
catch (const out_of_range& e) {
cerr << "Ошибка выхода за диапазон: " << e.what() << endl;
}
return 0;
}

В этом примере мы пытаемся получить доступ к элементу, который находится за пределами диапазона в нашем векторе. Функция at() вызывает исключение out_of_range, когда это происходит.

Определение новых исключений

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

Вот как можно определить свой собственный класс исключения:

#include <iostream>
#include <exception>
using namespace std;

class NegativeValueException : public exception {
public:
const char* what() const throw() {
return "Отрицательные значения не допускаются!";
}
};

double squareRoot(double x) {
if (x < 0) {
throw NegativeValueException();
}
return sqrt(x);
}

int main() {
try {
cout << squareRoot(25) << endl;  // Это будет работать без проблем
cout << squareRoot(-5) << endl;  // Это вызовет наше пользовательское исключение
}
catch (const NegativeValueException& e) {
cerr << "Ошибка: " << e.what() << endl;
}
return 0;
}

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

Итак, это всё, друзья! Мы осветили основы обработки исключений в C++. Помните, исключения — это ваши друзья. Они помогают вам писать более устойчивый и менее подверженный ошибкам код. Постоянно практикуйтесь, и скоро вы будете обрабатывать исключения как профи!

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

Credits: Image by storyset