目录
第一节:异常有哪些
第二节:异常相关关键字
2-1.抛出异常
2-2.捕获异常
2-3.异常的捕获规则
2-3-1.异常被最近的catch捕获
2-3-2.catch捕获的是异常的拷贝
2-3-3.异常为子类时,可以用父类引用接收
2-4.捕获任意异常
第三节:异常使用规范
下期预告:
第一节:异常有哪些
异常的本质是一个数据,它可以是内置类型、也可以是自定义类型,但是它的数据发生了出乎意料的错误,例如一个购票系统中的剩余票数肯定不是负数,如果发现是负数,说明程序出现了BUG,此时将剩余票数抛出。外部就会接收到这个数据,然后执行处理程序处理这个异常,或者由程序员来修复BUG。
总之,异常是一种错误的处理方式,让函数直接或者间接的调用者进行处理。
第二节:异常相关关键字
2-1.抛出异常
使用 throw 关键字来向外抛出异常:
void func1() {int a = 0;if (a == 0) // a为0就将其视作异常进行抛出throw a; // 异常抛出后不会再执行后面的函数,而是直接跳出异常std::cout << "我不会被执行了" << std::endl; }
当 func 被调用时,它就会抛出异常,异常的类型是a,值为0。
而且异常抛出后,该函数后面的代码也不会再执行了。
2-2.捕获异常
使用 try{} 与 catch{} 的组合来捕获并处理异常, try{} 中存放可能抛出异常的代码,catch{} 中存放获取异常后的处理函数:
void func1() {int a = 0;if (a == 0) // a为0就将其视作异常进行抛出throw a; // 异常抛出后不会再执行后面的函数,而是直接跳出异常std::cout << "我不会被执行了" << std::endl; } int main() {try{func1();}catch (int a) // 捕获的异常类型{std::cout << "捕获了异常:" << a << std::endl;}return 0; }
而且异常抛出后,对应 try{} 内的代码也不会继续向下执行了:
int main() {try{func1();std::cout << "抛出异常后,我不会再被执行了" << std::endl;}catch (int a) // 捕获的异常类型{std::cout << "捕获了异常:" << a << std::endl;}return 0; }
一个 catch{} 必须配对一个 try{},而一个 try{} 可以配对多个 catch{},这样才能捕获多种类型的异常进行处理:
void func1() {int a = 1; // a为1if (a == 0) // a为0就将其视作异常进行抛出throw a; } void func2() {std::string str = "123";if (str == "123")throw str; } int main() {try{func1();func2();}catch (int a) // 捕获的异常类型{std::cout << "捕获了异常:" << a << std::endl;}catch (std::string str){std::cout << "捕获了异常:" << str << std::endl;}return 0; }
为了能执行func2,将func1中的a设置为1,这样func1就不会抛出异常了。
2-3.异常的捕获规则
2-3-1.异常被最近的catch捕获
void func1() {int a = 0; if (a == 0)throw a; } void func2() {std::string str = "123";if (str == "123")throw str; } void func3() {try{func1();}catch (int a){std::cout << "我是func3的catch" << std::endl;} } int main() {try{func3();func2();}catch (int a) // 捕获的异常类型{std::cout << "捕获了异常:" << a << std::endl;}catch (std::string str){std::cout << "捕获了异常:" << str << std::endl;}return 0; }
func1嵌套了fun3,且func3也有对应的处理异常的catch,此时func1的异常就会被func3中的catch捕获并处理。而且异常被捕获后就不会继续向外抛出了,意味着main函数是没有异常的,它的try{}就会继续向后执行:
2-3-2.catch捕获的是异常的拷贝
catch捕获的是异常的一份拷贝,真正的异常在被捕获时就销毁了:
int main() {try{int a = 0;std::cout << &a << std::endl;throw a;}catch (int a) // 捕获的异常类型{std::cout << &a << std::endl;}return 0; }
2-3-3.异常为子类时,可以用父类引用接收
利用多态可以使用各种派生类定制的处理函数:
class Person { public:Person(const std::string& name = "") :_name(name){}virtual void PrintInfo(){std::cout << _name << std::endl;} private:std::string _name; };class Student:public Person { public:Student(const std::string& name) :_name(name){}virtual void PrintInfo() override{std::cout << _name << std::endl;} private:std::string _name; }; int main() {try{Student s("LiHua");throw s;}catch (Person& obj){obj.PrintInfo();}return 0; }
![]()
要保持多态性必须使用父类引用,接收子类,父类指针、单纯父类无引用都不行。
2-4.捕获任意异常
使用 catch(...){} 表示捕获任意异常,一般放在main函数最后,防止程序因为main函数抛出异常却没有相应的catch导致函数退出,进而导致程序崩溃。
还可以放在其他位置,使用:
void func() {try{//...}catch (...){throw;} }
表示捕获到什么异常,就抛出什么异常,catch(...){}作为保底来使用。
第三节:异常使用规范
将 noexcept 放在函数后面表示这个函数不会抛出异常,函数也就不能放进 try{} 里了,但是它内部还是可以处理异常的:
void func() noexcept {try{//...}catch (...){throw;} }
其次,在构造函数和析构函数中不要抛出异常,否则极易导致已分配的内存无法正确释放和资源不一致的问题。
最后,抛出异常时如果没有释放锁,很可能导致死锁问题,可以用智能锁解决
下期预告:
第二十章将详细了解智能指针。