21 C++异常
21.1 什么时候会发生异常
-
1.打开一个不存在的文件
-
2.请求存储空间失败
-
3.数组越界等等
21.2 使用abort()函数
-
1.包含在cstdlib头文件中,包含在std命名空间中
-
2.当调用abort()函数时,会引发异常并中断程序(Visual Studio 2019);
-
3.abort()函数是否刷新缓冲区取决于实现,程序员可以调用exit()以刷新缓冲区 相关函数:double hmean(double a, double b);
21.3 程序员手动处理
-
1.使用指针或引用计算值,如果遇到错误则返回一个错误的计算结果(一般是永远不会用到的值)
-
2.程序员偏向于使用指针,因为这样可以做出区分,如果是引用的话赋值语句语法一样就没什么区分 相关函数:bool hmean(double a, double b, double* ans);
21.4 异常处理机制
try catch语句:try检查是否会出现异常,catch捕获异常并处理。 首先执行到try,如果try语句块中的语句不引发异常,则直接跳过所有catch语句块;如果try语句块中的语句引发了异常,则检查异常是否与catch语句块括号里的类型一致,如果一致,则执行该catch语句块,如果不一致,则继续检查异常是否与catch语句块括号里的类型一致,以此类推,直到找到一致的catch语句块并执行该catch语句块;如果找不到合适的catch语句块,则使用默认异常处理方法。
本段程序使用的是 throw一个字符串,原则上throw任意数据类型都可,但是一般情况下程序员喜欢throw异常类 相关函数:double hmean1(double a, double b);
21.5 抛出异常类
选择throw异常类的原因:
-
1.可以使用不同的异常类型去区分不同的异常;
-
2.异常类可以携带异常信息
-
3.catch块可以使用异常类携带的异常信息 相关文件:exc_mean.h;相关类:bad_hmean and bad_gmean 相关函数:double hmean2(double a, double b); double gmean(double a, double b);
21.6 异常规范(Exception Specifications)
c++11被取消了
格式:throw()部分将出现在原型和函数定义中。
double harm(double a) throw(bad_thing); // may throw bad_thing exception double marm(double) throw(); // doesn't throw an exception
使用异常规范的原因:
-
1.异常规范的目的时提醒用户可能需要一个try块。
-
2.允许编译器添加代码来执行运行时检查,以查看是否违反了异常规范。
为什么被取消?marm()不引发异常,但可能在它调用的函数里面引发异常,也可能现在不引发异常但是将来系统更新后可能引发异常。
在C++程序员一致认为这种异常规范应该取缔。
但是C++11允许一种特别的规范:关键词 noexcept
double marm() noexcept; // marm() doesn't throw an exception
这表明该函数不会抛出异常,程序员认为知道函数不会抛出异常可以帮助编译器优化代码。
如果在运行时,noexecpt函数向外抛出了异常(如果函数内部捕捉了异常并完成处理,这种情况不算抛出异常),程序会直接终止,调用std::terminate()函数,该函数内部会调用std::abort()终止程序。
noexcept()操作符可以报告其操作数是否会引发异常。
21.7 展开堆栈
程序将调用函数指令的地址(返回地址)放在堆栈上,是先进后出的原则。函数参数、函数生成的自动变量、函数调用新函数的信息都被存储在堆栈上;当函数执行完成(return或正常执行完毕)时,其相关的变量、地址都将被弹出,以此类推,最终返回到最初调用函数的地方,每个函数调用都释放掉了占用的内存。 堆栈展开:考虑如果函数由于引发异常终止执行,堆栈的内容会发生什么变化?同样,程序从堆栈中释放内存。但是,程序不会在堆栈上的第一个返回地址处停止,而是继续释放堆栈,直到到达位于try块中的返回地址。
如果在函数调用的函数里面没有处理异常,那么要将异常抛到调用该函数的函数中(throw;)处理,直到处理为止,不然会引发断点。
相关文件:exc_mean.h;相关类:bad_hmean and bad_gmean
相关函数:double hmean2(double a, double b); double gmean(double a, double b); double means(double a, double b);
21.8 try...catch注意事项
-
1.throw会将异常传递到第一个能够处理该异常的try catch中
-
2.在throw异常时,编译器总是创建临时copy,原因是异常被抛出后,抛出异常的函数终止,该异常类也就不复存在;为什么catch(参数)中的参数是引用;因为引用可以使用一个基类引用接收继承类的对象,这就允许一个基类引用处理各个继承类的异常了。
class problem {...}; ... void super() throw (problem) { ... if (oh_no) { problem oops; // construct object throw oops; // throw it ... } ... try { super(); } catch(problem & p)//此处的p是个引用,而且指向异常的临时对象。 { // statements }
-
3.使用基类引用处理继承类异常时,如果需要一对一处理(每个继承类的处理方式不一样),则应该将最小的孩子(继承类)放在最前面的catch中,而将最基类的放在最后的catch中。
-
4.当不知道异常的类型时,可以使用默认catch异常。
try { duper(); } catch (...) // catch whatever is left { // statements }
21.9 异常类
-
1.exception类:在头文件exception.h或except.h中;这个类是C++中最基础的类,其他异常类都可以继承exception。 如果不想单独处理每个类,则可以使用exception类的引用catch该异常类。 基类exception有个what()方法,专门用于返回描述异常类的字符串,每次捕获异常是可以手动显示。
-
2.logic_error类:在头文件stdexcept中,继承exception类 logic_error是以下类的基类:
-
domain_error:引发函数中关于定义域或值域的异常;比如说sin()函数的定义域为(-00,+00),值域为[-1,1],如传递给sin()的值超过定义域,则可以引发域异常
-
invalid_argument:是告诉程序员一个意外的参数传递给了函数。比如只要求传递'0'或'1',如果传递其他字符,则可以引发非法异常。
-
length_error:指示没有足够的空间执行当前操作。比如将一个长度为10的字符串传递给长度为8的字符串,可以引发长度异常。
-
out_of_range:指示索引越界异常
-
-
3.runtime_error类:在头文件stdexcept中,继承exception类 runtime_error是以下类的基类:
-
range_error:数据超出指定范围引发range_error异常
-
overflow_error:主要用于整型或浮点类,当计算超过数据类型可表示的最大值时引发overflow_error异常
-
underflow_error:主要用于浮点类型数据,原因是浮点类型数据类型有最小可表示的数据,如果计算超过最小值,则引发underflow_error异常
-
-
4.bad_alloc异常类:用于指示在内存分配时可能发生的问题,共有继承自exception类 如果不想抛出bad_alloc异常,则可以使用new(std::nothrow)的方式,使用方法见示例
21.10 异常与继承
见头文件sales.h和实现文件sales.cpp
-
1.你可以继承一个异常类
-
2.你可以将异常类嵌套到别的类中
-
3.嵌套类也可以被继承
21.11 当异常无法控制的时候
主要是针对异常规范来说的,由于它在C++11中已经被取消了,所以可能用处不是很大(了解即可)
21.11.1 意外的异常
意外的异常:就是在异常规范中没有匹配的异常就叫做意外的异常
1.默认unexpected()处理意外的异常:首先调用unexpected()-->调用terminate()-->调用abort().
2.set_unexpected()修改terminate()函数:这两个函数都在头文件exception中,在std命名空间中:
typedef void (*unexpected_handler)(); unexpected_handler set_unexpected(unexpected_handler f) throw(); // C++98 unexpected_handler set_unexpected(unexpected_handler f) noexcept; // C++11 void unexpected(); // C++98 void unexpected() noexcept; // C+0x
(1)形参:set_unexpected()的参数unexpected_handler是一个函数指针,该指针指向的函数没有形参也没有返回值; (2)作用:使用set_unexpected()后terminate()将会调用set_unexpected()设置的函数而不再使用默认的terminate()函数 (3)注意事项:如果调用set_unexpected()多次,则terminate()采纳最后调用的set_unexpected() (4)举例:myUnexpected()函数作为terminate()调用的函数---不管怎么样都会引发异常导致程序终止(Visual 2019)
3.比set_terminate()更多的规则: unexpected_handler有两种选择: (1)使用默认terminate()终止程序 (2)抛出新异常 选择抛出新异常的结果取决于被unexpected_handler替换的异常和异常规范 (1)如果新抛出的异常与异常规范相匹配,则程序可以正常执行。 (2)如果新抛出的异常与异常规范不匹配,如果异常规范不包含std::bad_exception类型,则调用terminate() (3)如果新抛出的异常与异常规范不匹配,如果异常规范包含std::bad_exception(继承自exception类型并且声明在exception头文件中)类型,则不匹配的异常将会替换为std::bad_exception并处理
21.11.2 未捕获的异常
意外的异常逃过第一层阻碍,后面再无try catch可捕获该异常的即为未捕获的异常 1.默认terminate()处理未捕获异常:未捕获的异常会导致程序的终止,程序终止的过程是:首先调用terminate()-->默认情况下terminate()会调用abort() 2.set_terminate()修改terminate()函数:这两个函数都在头文件exception中,在std命名空间中:
typedef void (*terminate_handler)(); terminate_handler set_terminate(terminate_handler f) throw(); // C++98 terminate_handler set_terminate(terminate_handler f) noexcept; // C++11 void terminate(); // C++98 void terminate() noexcept; // C++11
(1)形参:set_terminate()的参数terminate_handler是一个函数指针,该指针指向的函数没有形参也没有返回值; (2)作用:使用set_terminate()后terminate()将会调用set_terminate()设置的函数而不再使用默认的terminate()函数 (3)注意事项:如果调用set_terminate()多次,则terminate()采纳最后调用的set_terminate() (4)举例:myQuit()函数作为terminate()调用的函数---不管怎么样都会引发异常导致程序终止(Visual 2019)
21.12 关于异常的注意事项
1.异常应该嵌入到程序中,而不是附加到程序中 2.使用异常增加了程序所占的存储空间,减慢了程序的运行,但是依然在一定程度上帮助程序员调试程序,减少错误。 3.将异常应用于模板不会很好因为不同的类型可能引发不同的异常,因此在使用时要小心。 4.将异常应用于动态内存分配也会出现一定的问题,因此在使用时要小心。 5.学习C++语言本身会帮助我们学习异常,学习异常也能帮助我们理解C++本身。
21.13 举例
代码:
main.h
#pragma once #ifndef _MAIN_H #define _MAIN_H #include "demo.h" #include "exc_mean.h" #include "sales.h" double hmean(double a, double b); bool hmean(double a, double b, double* ans); double hmean1(double a, double b); double hmean2(double a, double b); double gmean(double a, double b); // function prototypes double hmean(double a, double b); double gmean(double a, double b); double means(double a, double b); void myQuit(); void my_func() throw(int); void Argh(int*, int) throw(std::out_of_range, std::bad_exception); void myUnexpected(); double hmean(double a, double b) { if (a == -b) { std::cout << "untenable arguments to hmean()\n"; std::abort(); } else return 2.0 * a * b / (a + b); } bool hmean(double a, double b, double* ans) { if (a == -b) { *ans = DBL_MAX;//返回错误的值 return false; } else { *ans = 2.0 * a * b / (a + b); return true; } } double hmean1(double a, double b) { if (a == -b)//这里抛出了一个异常 throw "bad hmean() arguments: a = -b not allowed"; return 2.0 * a * b / (a + b); } double hmean2(double a, double b) { if (a == -b) throw bad_hmean(a, b); return 2.0 * a * b / (a + b); } double gmean(double a, double b) { if (a < 0 || b < 0) throw bad_gmean(a, b); return std::sqrt(a * b); } double means(double a, double b) { double am, hm, gm; demo d2("found in means()"); am = (a + b) / 2.0; // arithmetic mean try { hm = hmean2(a, b); gm = gmean(a, b); } catch (bad_hmean& bg) // start of catch block { bg.mesg(); std::cout << "Caught in means()\n"; throw; // rethrows the exception } d2.show(); return (am + hm + gm) / 3.0; } void myQuit() { std::cout << "Terminating due to uncaught exception\n"; exit(5); } void my_func() throw(int) { std::cout << "throw 1************************************" << std::endl; throw 1; } void myUnexpected() { throw std::bad_exception(); //or just throw; } void Argh(int* a, int b) throw(bad_hmean, std::bad_exception) { if (a[0] == b) { throw bad_hmean(1, -1); } else { std::cout << "else***" << std::endl; throw 1; } } #endif
demo.h
#pragma once class demo { private: std::string word; public: demo(const std::string& str) { word = str; std::cout << "demo " << word << " created\n"; } ~demo() { std::cout << "demo " << word << " destroyed\n"; } void show() const { std::cout << "demo " << word << " lives!\n"; } };
exc_mean.h
#pragma once /* 这是定义的两个异常类 */ #include <iostream> class bad_hmean { private: double v1; double v2; public: bad_hmean(double a = 0, double b = 0) : v1(a), v2(b) {} void mesg(); }; inline void bad_hmean::mesg() { std::cout << "hmean(" << v1 << ", " << v2 << "): " << "invalid arguments: a = -b\n"; } class bad_gmean { public: double v1; double v2; bad_gmean(double a = 0, double b = 0) : v1(a), v2(b) {} const char* mesg(); }; inline const char* bad_gmean::mesg() { return "gmean() arguments should be >= 0\n"; }
sales.h
#pragma once // sales.h -- exceptions and inheritance #include <stdexcept> #include <string> class Sales { public: enum { MONTHS = 12 }; // could be a static const class bad_index : public std::logic_error { private: int bi; // bad index value public: explicit bad_index(int ix, const std::string& s = "Index error in Sales object\n"); int bi_val() const { return bi; } virtual ~bad_index() throw() {} }; explicit Sales(int yy = 0); Sales(int yy, const double* gr, int n); virtual ~Sales() { } int Year() const { return year; } virtual double operator[](int i) const; virtual double& operator[](int i); private: double gross[MONTHS]; int year; }; class LabeledSales : public Sales { public: class nbad_index : public Sales::bad_index { private: std::string lbl; public: nbad_index(const std::string& lb, int ix, const std::string& s = "Index error in LabeledSales object\n"); const std::string& label_val() const { return lbl; } virtual ~nbad_index() throw() {} }; explicit LabeledSales(const std::string& lb = "none", int yy = 0); LabeledSales(const std::string& lb, int yy, const double* gr, int n); virtual ~LabeledSales() { } const std::string& Label() const { return label; } virtual double operator[](int i) const; virtual double& operator[](int i); private: std::string label; };
main.cpp
/* Project name : _17Exceptions Last modified Date: 2022年3月28日16点31分 Last Version: V1.0 Descriptions: 异常 什么时候会发生异常: 1.打开一个不存在的文件 2.请求存储空间失败 3.数组越界等等 */ #include<iostream> #include<cstdlib> #include <new> #include <exception> #include<stdexcept> #include<vector> #include "main.h" int main() { /* 使用abort()函数: 1.包含在cstdlib头文件中,包含在std命名空间中 2.当调用abort()函数时,会引发异常并中断程序(Visual Studio 2019); 3.abort()函数是否刷新缓冲区取决于实现,程序员可以调用exit()以刷新缓冲区 相关函数:double hmean(double a, double b); */ std::cout << "abort()***********************************************************"<<std::endl; double x, y, z; std::cout << "Enter two numbers: "; while (std::cin >> x >> y) { z = hmean(x, y); std::cout << "Harmonic mean of " << x << " and " << y << " is " << z << std::endl; std::cout << "Enter next set of numbers <q to quit>: "; } std::cin.clear();//由于输入类型不匹配然后cin被锁了,所以重置cin std::cin.get();//读取最后的那个回车符,防止cin后面继续被锁 /* 程序员手动处理: 1.使用指针或引用计算值,如果遇到错误则返回一个错误的计算结果(一般是永远不会用到的值) 2.程序员偏向于使用指针,因为这样可以做出区分,如果是引用的话赋值语句语法一样就没什么区分 相关函数:bool hmean(double a, double b, double* ans); */ std::cout << "程序员手动处理****************************************************" << std::endl; std::cout << "Enter two numbers: "; while (std::cin >> x >> y) { if (hmean(x, y, &z)) std::cout << "Harmonic mean of " << x << " and " << y << " is " << z << std::endl; else std::cout << "One value should not be the negative " << "of the other - try again.\n"; std::cout << "Enter next set of numbers <q to quit>: "; } /* * 异常处理机制: try catch语句:try检查是否会出现异常,catch捕获异常并处理 首先执行到try,如果try语句块中的语句不引发异常,则直接跳过所有catch语句块;如果try语句块中的语句引发了异常, 则检查异常是否与catch语句块括号里的类型一致,如果一致,则执行该catch语句块,如果不一致,则继续检查异常 是否与catch语句块括号里的类型一致,以此类推,直到找到一致的catch语句块并执行该catch语句块;如果找不到合适 的catch语句块,则使用默认异常处理方法。 本段程序使用的是 throw一个字符串,原则上throw任意数据类型都可,但是一般情况下程序员喜欢throw异常类 相关函数:double hmean1(double a, double b); */ std::cout << "try catch************************************************************" << std::endl; std::cin.clear();//由于输入类型不匹配然后cin被锁了,所以重置cin std::cin.get();//读取最后的那个回车符,防止cin后面继续被锁 std::cout << "Enter two numbers: "; while (std::cin >> x >> y) { try { // start of try block z = hmean1(x, y); } // end of try block catch (const char* s) // start of exception handler { std::cout << s << std::endl; std::cout << "Enter a new pair of numbers: "; continue; } // end of handler std::cout << "Harmonic mean of " << x << " and " << y << " is " << z << std::endl; std::cout << "Enter next set of numbers <q to quit>: "; } /* 抛出异常类: 选择throw异常类的原因: 1.可以使用不同的异常类型去区分不同的异常; 2.异常类可以携带异常信息 3.catch块可以使用异常类携带的异常信息 相关文件:exc_mean.h;相关类:bad_hmean and bad_gmean 相关函数:double hmean2(double a, double b); double gmean(double a, double b); */ std::cout << "抛出异常类*********************************************************" << std::endl; std::cin.clear();//由于输入类型不匹配然后cin被锁了,所以重置cin std::cin.get();//读取最后的那个回车符,防止cin后面继续被锁 std::cout << "Enter two numbers: "; while (std::cin >> x >> y) { try { // start of try block z = hmean2(x, y); std::cout << "Harmonic mean of " << x << " and " << y << " is " << z << std::endl; std::cout << "Geometric mean of " << x << " and " << y << " is " << gmean(x, y) << std::endl; std::cout << "Enter next set of numbers <q to quit>: "; }// end of try block catch (bad_hmean& bg) // start of catch block { bg.mesg(); std::cout << "Try again.\n"; continue; } catch (bad_gmean& hg) { std::cout << hg.mesg(); std::cout << "Values used: " << hg.v1 << ", " << hg.v2 << std::endl; std::cout << "Sorry, you don't get to play any more.\n"; break; } // end of catch block } /* 异常规范(Exception Specifications):c++11被取消了 格式:double harm(double a) throw(bad_thing); // may throw bad_thing exception double marm(double) throw(); // doesn't throw an exception 为什么被取消?harm()可能会引发bad_thing异常,也可能是在它调用的函数里面引发的异常,也可能现在不引发异常但是将来系统更新后可能引发异常。 在C++程序员俱乐部大家一致认为这种异常规范应该取缔。 但是C++11允许一种特别的规范:关键词 noexcept double marm() noexcept; // marm() doesn't throw an exception 这表明该函数不会抛出异常。 noexcept()操作符可以报告其操作数是否会引发异常。 */ /* Unwinding the Stack(展开堆栈): 程序将调用函数指令的地址(返回地址)放在堆栈上,是先进后出的原则。函数参数、函数生成的自动变量、函数调用新函数的信息都被存储在堆栈上; 当函数执行完成(return或正常执行完毕)时,其相关的变量、地址都将被弹出,以此类推,最终返回到最初调用函数的地方,每个函数调用都释放掉了占用的内存。 考虑如果函数由于引发异常终止执行,堆栈的内容会发生什么变化,堆栈上关于try与throw之间生成的所有参数、函数地址、自动变量等等都会被弹出; 这就叫做展开堆栈(Unwinding the Stack);这个机制是为了保证try与throw之间占用的内存被正常释放。 如果在函数调用的函数里面没有处理异常,那么要将异常抛到调用该函数的函数中(throw;)处理,直到处理为止,不然会引发断点。 相关文件:exc_mean.h;相关类:bad_hmean and bad_gmean 相关函数:double hmean2(double a, double b); double gmean(double a, double b); double means(double a, double b); */ /* 注意事项: 1.throw会将异常传递到第一个能够处理该异常的try catch中 2.在throw异常时,编译器总是创建临时copy,原因是catch(参数)中的参数是引用; 但是为什么要使用引用而不用对象呢?因为引用可以使用一个基类引用接收继承类的对象,这就允许一个基类引用处理各个继承类的异常了。 3.使用基类引用处理继承类异常时,如果需要一对一处理(每个继承类的处理方式不一样),则应该将最小的孩子(继承类)放在最前面的catch中,而将最基类的放在最后的catch中。 */ std::cout << "Unwinding the Stack******************************************************" << std::endl; std::cin.clear();//由于输入类型不匹配然后cin被锁了,所以重置cin std::cin.get();//读取最后的那个回车符,防止cin后面继续被锁 demo d1("found in block in main()"); std::cout << "Enter two numbers: "; while (std::cin >> x >> y) { try { // start of try block z = means(x, y); std::cout << "The mean mean of " << x << " and " << y << " is " << z << std::endl; std::cout << "Enter next pair: "; } // end of try block catch (bad_hmean& bg) // start of catch block { bg.mesg(); std::cout << "Try again.\n"; continue; } catch (bad_gmean& hg) { std::cout << hg.mesg(); std::cout << "Values used: " << hg.v1 << ", " << hg.v2 << std::endl; std::cout << "Sorry, you don't get to play any more.\n"; break; } // end of catch block } d1.show(); /* 异常类: 1.exception类:在头文件exception.h或except.h中;这个类是C++中最基础的类,其他异常类都可以继承exception。 如果不想单独处理每个类,则可以使用exception类的引用catch该异常类。 基类exception有个what()方法,专门用于返回描述异常类的字符串,每次捕获异常是可以手动显示。 2.logic_error类:在头文件stdexcept中,继承exception类 logic_error是以下类的基类: domain_error:引发函数中关于定义域或值域的异常;比如说sin()函数的定义域为(-00,+00),值域为[-1,1],如传递给sin()的值超过定义域,则可以引发域异常 invalid_argument:是告诉程序员一个意外的参数传递给了函数。比如只要求传递'0'或'1',如果传递其他字符,则可以引发非法异常。 length_error:指示没有足够的空间执行当前操作。比如将一个长度为10的字符串传递给长度为8的字符串,可以引发长度异常。 out_of_range:指示索引越界异常 3.runtime_error类:在头文件stdexcept中,继承exception类 runtime_error是以下类的基类: range_error:数据超出指定范围引发range_error异常 overflow_error:主要用于整型或浮点类,当计算超过数据类型可表示的最大值时引发overflow_error异常 underflow_error:主要用于浮点类型数据,原因是浮点类型数据类型有最小可表示的数据,如果计算超过最小值,则引发underflow_error异常 4.bad_alloc异常类:用于指示在内存分配时可能发生的问题,共有继承自exception类 如果不想抛出bad_alloc异常,则可以使用new(std::nothrow)的方式,使用方法见示例 */ std::cout << "异常类*********************************************************" << std::endl; std::cout << "throw bad_alloc************************************************" << std::endl; struct Big { double stuff[20000]; }; Big* pb; try { std::cout << "Trying to get a big block of memory:\n"; pb = new Big[10000]; // 1,600,000,000 bytes std::cout << "Got past the new request:\n"; } catch (std::bad_alloc& ba) { std::cout << "Caught the exception!\n"; std::cout << ba.what() << std::endl; exit(EXIT_FAILURE); } std::cout << "Memory successfully allocated\n"; pb[0].stuff[0] = 4; std::cout << pb[0].stuff[0] << std::endl; delete[] pb; std::cout << "new(std::nothrow)************************************************" << std::endl; pb = new(std::nothrow) Big[10000]; // 1,600,000,000 bytes if (pb == 0) { std::cout << "Could not allocate memory. Bye.\n"; exit(EXIT_FAILURE); } /* 异常与继承:见头文件sales.h和实现文件sales.cpp 1.你可以继承一个异常类 2.你可以将异常类嵌套到别的类中 3.嵌套类也可以被继承 */ std::cout << "异常与继承*********************************************************" << std::endl; double vals1[12] = { 1220, 1100, 1122, 2212, 1232, 2334, 2884, 2393, 3302, 2922, 3002, 3544 }; double vals2[12] = { 12, 11, 22, 21, 32, 34, 28, 29, 33, 29, 32, 35 }; Sales sales1(2011, vals1, 12); LabeledSales sales2("Blogstar", 2012, vals2, 12); std::cout << "First try block:\n"; try { int i; std::cout << "Year = " << sales1.Year() << std::endl; for (i = 0; i < 12; ++i) { std::cout << sales1[i] << ' '; if (i % 6 == 5) std::cout << std::endl; } std::cout << "Year = " << sales2.Year() << std::endl; std::cout << "Label = " << sales2.Label() << std::endl; for (i = 0; i <= 12; ++i) { std::cout << sales2[i] << ' '; if (i % 6 == 5) std::cout << std::endl; } std::cout << "End of try block 1.\n"; } catch (LabeledSales::nbad_index& bad) { std::cout << bad.what(); std::cout << "Company: " << bad.label_val() << std::endl; std::cout << "bad index: " << bad.bi_val() << std::endl; } catch (Sales::bad_index& bad) { std::cout << bad.what(); std::cout << "bad index: " << bad.bi_val() << std::endl; } std::cout << "\nNext try block:\n"; try { sales2[2] = 37.5; sales1[20] = 23345; std::cout << "End of try block 2.\n"; } catch (LabeledSales::nbad_index& bad) { std::cout << bad.what(); std::cout << "Company: " << bad.label_val() << std::endl; std::cout << "bad index: " << bad.bi_val() << std::endl; } catch (Sales::bad_index& bad) { std::cout << bad.what(); std::cout << "bad index: " << bad.bi_val() << std::endl; } /* 当异常无法控制的时候:主要是针对异常规范来说的,由于它在C++11中已经被取消了,所以可能用处不是很大(了解即可) 意外的异常:就是在异常规范中没有匹配的异常就叫做意外的异常 1.默认unexpected()处理意外的异常:首先调用unexpected()-->调用terminate()-->调用abort(). 2.set_unexpected()修改terminate()函数:这两个函数都在头文件exception中,在std命名空间中: typedef void (*unexpected_handler)(); unexpected_handler set_unexpected(unexpected_handler f) throw(); // C++98 unexpected_handler set_unexpected(unexpected_handler f) noexcept; // C++11 void unexpected(); // C++98 void unexpected() noexcept; // C+0x (1)形参:set_unexpected()的参数unexpected_handler是一个函数指针,该指针指向的函数没有形参也没有返回值; (2)作用:使用set_unexpected()后terminate()将会调用set_unexpected()设置的函数而不再使用默认的terminate()函数 (3)注意事项:如果调用set_unexpected()多次,则terminate()采纳最后调用的set_unexpected() (4)举例:myUnexpected()函数作为terminate()调用的函数---不管怎么样都会引发异常导致程序终止(Visual 2019) 3.比set_terminate()更多的规则: unexpected_handler有两种选择: (1)使用默认terminate()终止程序 (2)抛出新异常 选择抛出新异常的结果取决于被unexpected_handler替换的异常和异常规范 (1)如果新抛出的异常与异常规范相匹配,则程序可以正常执行。 (2)如果新抛出的异常与异常规范不匹配,如果异常规范不包含std::bad_exception类型,则调用terminate() (3)如果新抛出的异常与异常规范不匹配,如果异常规范包含std::bad_exception(继承自exception类型并且声明在exception头文件中)类型,则不匹配的异常将会替换为std::bad_exception并处理 未捕获的异常:意外的异常逃过第一层阻碍,后面再无try catch可捕获该异常的即为未捕获的异常 1.默认terminate()处理未捕获异常:未捕获的异常会导致程序的终止,程序终止的过程是:首先调用terminate()-->默认情况下terminate()会调用abort() 2.set_terminate()修改terminate()函数:这两个函数都在头文件exception中,在std命名空间中: typedef void (*terminate_handler)(); terminate_handler set_terminate(terminate_handler f) throw(); // C++98 terminate_handler set_terminate(terminate_handler f) noexcept; // C++11 void terminate(); // C++98 void terminate() noexcept; // C++11 (1)形参:set_terminate()的参数terminate_handler是一个函数指针,该指针指向的函数没有形参也没有返回值; (2)作用:使用set_terminate()后terminate()将会调用set_terminate()设置的函数而不再使用默认的terminate()函数 (3)注意事项:如果调用set_terminate()多次,则terminate()采纳最后调用的set_terminate() (4)举例:myQuit()函数作为terminate()调用的函数---不管怎么样都会引发异常导致程序终止(Visual 2019) */ //对于下面的语句,我的系统就是会引发“0x765EB922 处(位于 _17Exceptions.exe 中)有未经处理的异常: Microsoft C++ 异常: int,位于内存位置 0x00FDF42C 处。”异常,我也不知道为什么 /*std::set_terminate(myQuit); my_func();*/ //对于下面的语句,我的系统也引发了“0x765EB922 处(位于 _17Exceptions.exe 中)有未经处理的异常: Microsoft C++ 异常: int,位于内存位置 0x0038F490 处。”异常,我也不知道什么 /*set_unexpected(myUnexpected); try { int a[2] = { 1,2 }; Argh(a, 2); } catch (bad_hmean& ex) { std::cout << "bad_hmean*****************************" << std::endl; } catch (std::bad_exception& ex) { std::cout << "bad_exception*****************************" << std::endl; }*/ /* 关于异常的注意事项: 1.异常应该嵌入到程序中,而不是附加到程序中 2.使用异常增加了程序所占的存储空间,减慢了程序的运行,但是依然在一定程度上帮助程序员调试程序,减少错误。 3.将异常应用于模板不会很好因为不同的类型可能引发不同的异常,因此在使用时要小心。 4.将异常应用于动态内存分配也会出现一定的问题,因此在使用时要小心。 5.学习C++语言本身会帮助我们学习异常,学习异常也能帮助我们理解C++本身。 */ return 0; }
运行结果:
abort()*********************************************************** Enter two numbers: 99 88 Harmonic mean of 99 and 88 is 93.1765 Enter next set of numbers <q to quit>: q 程序员手动处理**************************************************** Enter two numbers: 99 88 Harmonic mean of 99 and 88 is 93.1765 Enter next set of numbers <q to quit>: q try catch************************************************************ Enter two numbers: 99 88 Harmonic mean of 99 and 88 is 93.1765 Enter next set of numbers <q to quit>: q 抛出异常类********************************************************* Enter two numbers: 99 88 Harmonic mean of 99 and 88 is 93.1765 Geometric mean of 99 and 88 is 93.3381 Enter next set of numbers <q to quit>: q Unwinding the Stack****************************************************** demo found in block in main() created Enter two numbers: 99 88 demo found in means() created demo found in means() lives! demo found in means() destroyed The mean mean of 99 and 88 is 93.3382 Enter next pair: q demo found in block in main() lives! 异常类********************************************************* throw bad_alloc************************************************ Trying to get a big block of memory: Caught the exception! bad allocation D:\Prj\_C++Self\_17Exceptions\Debug\_17Exceptions.exe (进程 8964)已退出,代码为 1。 要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。 按任意键关闭此窗口. . .