11C++异常处理

异常处理

1.简介

与java一样,程序运行时常会碰到一些错误,如果不处理,很可能导致程序崩溃。C++ 异常处理机制就可以让我们捕获并处理这些错误

C++ 异常处理机制会涉及 try、catch、throw 三个关键字。

2.常见运行时错误

运行时错误是指程序在运行期间发生的错误,例如除数为 0、内存分配失败、数组越界、文件不存在等。C++ 异常(Exception)机制就是为解决运行时错误而引入的。

案例:

#include <iostream>
#include <string>
using namespace std;
int main(){
    string str = "http://c.biancheng.net";
    char ch1 = str[100];  //下标越界,ch1为垃圾值
    cout<<ch1<<endl;
    char ch2 = str.at(100);  //下标越界,抛出异常
    cout<<ch2<<endl;
    return 0;
}

str[]😕/不会检查下标越界,不会抛出异常,所以即使有错误,try 也检测不到

//换句话说,发生异常时必须将异常明确地抛出,try 才能检测到;如果不抛出来,即使有异常 try 也检测不到。所谓抛出异常,就是明确地告诉程序发生了什么错误。

try-catch抓捕异常

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

int main(int argc, char const *argv[])
{
    string str = "http://c.biancheng.net";
    try
    {
        char ch1 = str[100];//因为[]不会检查下标越界,不会抛出异常,所以即使有错误,try 也检测不到
        //换句话说,发生异常时必须将异常明确地抛出,try 才能检测到;如果不抛出来,即使有异常 try 也检测不到。所谓抛出异常,就是明确地告诉程序发生了什么错误。
        cout << ch1 << endl;
    }
    catch (exception e)
    {
        cout << "[1]out of bound!" << endl;
    }
    try
    {
        char ch2 = str.at(100);//推荐使用at,会检查下标越界
        cout << ch2 << endl;
    }
    catch (exception &e)
    { //exception类位于<exception>头文件中
        cout << "[2]out of bound!" << endl;
    }
    system("pause");
    return 0;
}

3.抛出异常throw

throw 可以抛出异常。自定义的异常

#include <iostream>
#include <string>
#include <exception>
using namespace std;
void func()
{
    throw "Unknown Exception"; //抛出异常
    cout << "[1]This statement will not be executed." << endl;
}
int main()
{
    try
    {
        func();
        cout << "[2]This statement will not be executed." << endl;
    }
    catch (const char *&e)
    {
        cout << e << endl;
    }
    system("pause");
    return 0;
}

4.catch异常类型

异常既然是一份数据,那么就应该有数据类型。C++ 规定,异常类型可以是 int、char、float、bool 等基本类型,也可以是指针、数组、字符串、结构体、类等聚合类型。C++ 语言本身以及标准库中的函数抛出的异常,都是 exception 类或其子类的异常。也就是说,抛出异常时,会创建一个 exception 类或其子类的对象。

我们可以将 catch 看做一个没有返回值的函数,当异常发生后 catch 会被调用,并且会接收实参(异常数据)。

总的来说,catch 和真正的函数调用相比,多了一个「在运行阶段将实参和形参匹配」的过程。

————————与java基本一致,注意,在C++眼中,除以0不可接受的,不应当作为异常处理。

#include <iostream>
#include <string>
using namespace std;
class Base
{
};
class Derived : public Base
{
};
int main()
{
    try
    {
        throw Derived(); //抛出自己的异常类型,实际上是创建一个Derived类型的匿名对象
        cout << "This statement will not be executed." << endl;
    }
    catch (int)
    {
        cout << "Exception type: int" << endl;
    }
    catch (char *)
    {
        cout << "Exception type: cahr *" << endl;
    }
    catch (Base)
    { //匹配成功(向上转型)
        cout << "Exception type: Base" << endl;
    }
    catch (Derived)
    {
        cout << "Exception type: Derived" << endl;
    }
    system("pause");
    return 0;
}

5.catch在匹配过程中类型转换

C/C++ 中存在多种多样的类型转换,以普通函数(非模板函数)为例,发生函数调用时,如果实参和形参的类型不是严格匹配,那么会将实参的类型进行适当的转换,以适应形参的类型,这些转换包括:

  • 算数转换:例如 int 转换为 float,char 转换为 int,double 转换为 int 等。
  • 向上转型:也就是派生类向基类的转换,请猛击《C++向上转型(将派生类赋值给基类)》了解详情。
  • const 转换:也即将非 const 类型转换为 const 类型,例如将 char * 转换为 const char *。
  • 数组或函数指针转换:如果函数形参不是引用类型,那么数组名会转换为数组指针,函数名也会转换为函数指针。
  • 用户自定的类型转换。
    catch 在匹配异常类型的过程中,也会进行类型转换,但是这种转换受到了更多的限制,仅能进行「向上转型」、「const 转换」和「数组或函数指针转换」,其他的都不能应用于 catch。

案例:

#include <iostream>
using namespace std;
int main()
{
    int nums[] = {1, 2, 3};
    try
    {
        throw nums;
        cout << "This statement will not be executed." << endl;
    }
    catch (const int *)
    {
        cout << "Exception type: const int *" << endl;
    }
    system("pause");
    return 0;
}

Exception type: const int *
请按任意键继续. . .

5.throw 抛出异常规范

throw 关键字除了可以用在函数体中抛出异常,还可以用在函数头和函数体之间,指明当前函数能够抛出的异常类型,这称为异常规范。

double func (char param) throw (int);

这条语句声明了一个名为 func 的函数,它的返回值类型为 double,有一个 char 类型的参数,并且只能抛出 int 类型的异常。如果抛出其他类型的异常,try 将无法捕获,只能终止程序。

如果函数会抛出多种类型的异常,那么可以用逗号隔开:

double func (char param) throw (int, char, exception);

如果函数不会抛出任何异常,那么( )中什么也不写:

double func (char param) throw ();

如此,func() 函数就不能抛出任何类型的异常了,即使抛出了,try 也检测不到。

1.虚函数中的异常规范

C++ 规定,派生类虚函数的异常规范必须与基类虚函数的异常规范一样严格,或者更严格。只有这样,当通过基类指针(或者引用)调用派生类虚函数时,才能保证不违背基类成员函数的异常规范。请看下面的例子:

class Base{
public:
    virtual int fun1(int) throw();
    virtual int fun2(int) throw(int);
    virtual string fun3() throw(int, string);
};
class Derived:public Base{
public:
    int fun1(int) throw(int);   //错!异常规范不如 throw() 严格
    int fun2(int) throw(int);   //对!有相同的异常规范
    string fun3() throw(string);  //对!异常规范比 throw(int,string) 更严格
}
2) 异常规范与函数定义和函数声明

C++ 规定,异常规范在函数声明和函数定义中必须同时指明,并且要严格保持一致,不能更加严格或者更加宽松。

简单说:与声明保持一致。

//错!定义中有异常规范,声明中没有
void func1();
void func1() throw(int) { }
//错!定义和声明中的异常规范不一致
void func2() throw(int);
void func2() throw(int, bool) { }
//对!定义和声明中的异常规范严格一致
void func3() throw(float, char*);
void func3() throw(float, char*) { }

注意:请抛弃异常规范,不要再使用它

异常规范的初衷是好的,它希望让程序员看到函数的定义或声明后,立马就知道该函数会抛出什么类型的异常,这样程序员就可以使用 try-catch 来捕获了。如果没有异常规范,程序员必须阅读函数源码才能知道函数会抛出什么异常。

不过这有时候也不容易做到。例如,func_outer() 函数可能不会引发异常,但它调用了另外一个函数 func_inner(),这个函数可能会引发异常。再如,您编写的函数调用了老式的库函数,此时不会引发异常,但是库更新以后这个函数却引发了异常。总之,异常规范的初衷实现起来有点困难,所以大家达成的一致意见是,最好不要使用异常规范。

异常规范是 C++98 新增的一项功能,但是后来的 C++11 已经将它抛弃了,不再建议使用。

6.exception类

C++异常基类。C++语言本身或者标准库抛出的异常都是 exception 的子类,称为标准异常(Standard Exception)。

 class exception
  {
  public:
    exception() _GLIBCXX_USE_NOEXCEPT { } 构造函数
    virtual ~exception() _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_USE_NOEXCEPT;//虚析构函数

    virtual const char*
    what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_USE_NOEXCEPT; //虚函数
  };

std::exception 该异常是所有标准 C++ 异常的父类。
std::bad_alloc 该异常可以通过 new 抛出。
std::bad_cast 该异常可以通过 dynamic_cast 抛出。
std::bad_exception 这在处理 C++ 程序中无法预期的异常时非常有用。
std::bad_typeid 该异常可以通过 typeid 抛出。
std::logic_error 理论上可以通过读取代码来检测到的异常。
std::domain_error 当使用了一个无效的数学域时,会抛出该异常。
std::invalid_argument 当使用了无效的参数时,会抛出该异常。
std::length_error 当创建了太长的 std::string 时,会抛出该异常。
std::out_of_range 该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator
std::runtime_error 理论上不可以通过读取代码来检测到的异常。
std::overflow_error 当发生数学上溢时,会抛出该异常。
std::range_error 当尝试存储超出范围的值时,会抛出该异常。
std::underflow_error 当发生数学下溢时,会抛出该异常。

7.泛型捕获

void function(){  
    try {  
        /* your code */  
    }catch(...) {  
        /* handler */
    }
}

8.自定义异常类型

#include <iostream>
using namespace std;

class MyException : public exception
{
public:
    MyException();
    MyException(string emsg);
    ~MyException(){};
    virtual const char *what() const throw();

protected:
    string emsg;
};

MyException::MyException() : exception()
{
}

MyException::MyException(string emsg)
{
    this->emsg = emsg;
}

const char *MyException::what() const throw()
{
    return this->emsg.c_str();
}

void test()
{
    MyException my("数据错误");
    throw my;
    cout << "不会执行" << endl;
}

int main(int argc, char const *argv[])
{
    try
    {
        test();
    }
    catch (const MyException &e)
    {
        cout << e.what() << endl;
    }
    system("pause");
    return 0;
}

数据错误
请按任意键继续. . .

9.总结

​ catch:可以直接抓捕要抛出的类型包括可能的转换类型,相比java更加直接,java需要包裹exception,c++不需要

​ exception: 不同于java,它的类型大部分不会自己打印,而是需要自定义,java中,有各种系统帮我们定义好的异常。

argv[])
{
try
{
test();
}
catch (const MyException &e)
{
cout << e.what() << endl;
}
system(“pause”);
return 0;
}


数据错误
请按任意键继续. . .

### 9.总结

​	catch:可以直接抓捕要抛出的类型包括可能的转换类型,相比java更加直接,java需要包裹exception,c++不需要

​	exception: 不同于java,它的类型大部分不会自己打印,而是需要自定义,java中,有各种系统帮我们定义好的异常。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值