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。 要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。 按任意键关闭此窗口. . .



















