C++ Primer Plus 学习笔记 第十五章 异常 abort() 返回错误提示,try-throw-catch exception和其派生类 RTTI 类型转换运算符

本文主要是介绍C++ Primer Plus 学习笔记 第十五章 异常 abort() 返回错误提示,try-throw-catch exception和其派生类 RTTI 类型转换运算符,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

abort() 来源于cstdlib 或 stdlib.h中 手动调用该函数来终止程序。 程序会跳出如下信息然后程序终止

untenable arguments to hmean()This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

或者使用手动判断异常的方式 免去程序强制退出

error2.cpp

#include <iostream>
#include <cfloat>bool hmean(double a, double b, double * ans);int main()
{double x, y, z;std::cout << "Enter two numbers: ";while (std::cin >> x >> y){if (hmean(x, y, &z))std::cout << "Hearmonic mean of " << x << " and " << y<< " is " << z << std::endl;elsestd::cout << "One value should not be the negative "<< "of the other - try again.\n";std::cout << "Enter next set of numbers <q to quit>: ";}std::cout << "Bye!\n";return 0;
}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;}
}

异常机制三部曲

引发异常,使用处理程序捕获异常,使用try块

这个在大部分的编程语言中都通用

throw() 抛异常

try() 可能会引发异常的代码块

catch() 异常捕获处理。

这个在JAVA Python中是一样的 只是关键字有区别

程序示例:

#include <iostream>
double hmean(double a, double b);int main()
{double x, y, z;std::cout << "Enter two numbers: ";while(std::cin >> x >> y){try{z = hmean(x, y);}catch(const char * s){std::cout << s << std::endl;std::cout << "Enter a new pair of numbers: ";continue;}std::cout << "Harmonic mean of " << x << " and " << y<< " is " << z << std::endl;std::cout << "Enter next set of numbers <q to quit>: ";}std::cout << "Bye!\n";
}double hmean(double a, double b)
{if (a == -b)throw "bad hmean() arguments: a = -b not allowed";return 2.0 * a * b / (a + b);
}

如果没有捕获 C++ 默认调用abort()

将对象用作异常类型:

exc_mean.h

#include <iostream>// bad_hmean异常是判断是否为相反数
// bad_gmean异常是判断参数是否小于0
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";
}

error4.cpp

#include <iostream>
#include <cmath>
#include "exc_mean.h"// 在各自函数中引发异常
double hmean(double a, double b);
double gmean(double a, double b);
int main()
{using std::cout;using std::cin;using std::endl;double x, y, z;cout << "Enter two numbers: ";while (cin >> x >> y){try{z = hmean(x, y);cout << "Harmonic mean of " << x << " and " << y<< " is " << z << endl;cout << "Geometric mean of " << x << " and " << y<< " is " << gmean(x, y)  << endl;cout << "Enter next set of numbers <q to quit>: ";}
// 通过捕获异常对象的引用类型来确定是用哪个catchcatch(bad_hmean & bg){bg.mesg();cout << "Try again.\n";continue;}catch(bad_gmean & hg){cout << hg.mesg();cout << "Values used: " << hg.v1 << ", "<< hg.v2 << endl;cout << "Sorry, you don't get to play any more.\n";break;}}cout << "Bye!\n";return 0;
}double hmean(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);
}

异常规范:

noexcept 指出所在行的代码不会出现异常 例如:

double marm() noexcept; 

但是 谁能保证呢  发誓都没用的  所以 C++11也建议不要用这玩意儿

同时还有一个函数 noexcept() 用来判断操作数是否会引发异常。

 

栈解退:

正常的函数调用是这样的

如果调用时有异常是这样的:

示例图

程序示例

exc_mean.h

#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";
}

error5.cpp

#include <iostream>
#include <cmath>
#include <string>
#include "exc_mean.h"// 此DEMO是用来展示程序执行到哪一步
class demo
{private:std::string word;public:demo (const std::string & str){word = str;std::cout << "demo " << word << " create\n";}~demo(){std::cout << "demo " << word << " destroyed\n";}void show() const{std::cout << "demo " << word << " lives!\n";}
};double hmean(double a, double b);
double gmean(double a, double b);
double means(double a, double b);int main()
{using std::cout;using std::cin;using std::endl;double x, y, z;{demo d1("found in block in main()");cout << "Enter two numbers: ";while(cin >> x >> y){try{z = means(x, y);cout << "The mean mean of " << x << " and " << y<< " is " << z << endl;cout << "Enter next pair: ";}catch(bad_hmean & bg){bg.mesg();cout << "Try again.\n";continue;}catch(bad_gmean & hg){cout << hg.mesg();cout << "Values used: " << hg.v1 << ", "<< hg.v2 << endl;cout << "Soryy, you don't get to play any more.\n";break;}}d1.show();}cout << "Bye!\n";cin.get();cin.get();return 0;
}double hmean(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;try{
// 这里会捕获两次异常 一次是means自身捕获的 还有一次是它抛出去的然后由main()捕获的hm = hmean(a, b);
// 这里如果出现异常直接抛给main() 因为当前函数中没有对此异常进行捕获gm = gmean(a, b);}catch (bad_hmean & bg){bg.mesg();std::cout << "Caught in means()\n";
// 这里抛给main() 一旦出现异常 函数后面的内容就不会执行。 如果函数有调用对象则直接调用解析函数throw;}d2.show();return (am + hm + gm) / 3.0;
}

执行结果

程序说明

异常的

异常的其他特性;

异常抛出来的总是对象的拷贝 以免对象是在函数中创建的临时对象而被释放掉造成错误

异常捕获使用引用的原因是 可以引用异常的派生类 这样可以捕获一系列的同一基类的异常 

但是 catch的排列顺序要与派生顺序相反。

exception类 

是一个抽象异常类

通过重新定义该类的what()函数 返回相应的字符串

通过此类派生了stdexcept类

该类又派生出两个系列的基类

logic_error和runtime_error

logic_error:

runtime_error

bad_alloc异常和new

new导致的分配问题会引发bad_alloc异常 <new>中已经包含了该异常的声明

程序示例:

#include <iostream>
#include <new>
#include <cstdlib>
using namespace std;struct Big
{double stuff[20000];
};int main()
{Big* pd;try{cout << "Trying to get a big block of memory:\n";pd = new Big[10000];cout << "Got past the new request:\n";}catch (bad_alloc & ba){cout << "Caught the exception!\n";cout << ba.what() << endl;exit(EXIT_FAILURE);}cout << "Memory successfully allocated\n";pd[0].stuff[0] = 4;cout << pd[0].stuff[0] << endl;delete [] pd;return 0;
}

 

该程序我没有正常调出bad_alloc异常 因为在编译的时候 编译器就告诉我太大了 [手动无奈]

如果希望new失败的时候返回的是空指针 就这么写

int* pi = new (std::nothrow) int;

异常,继承和类

嗯。说的就是普通的类中带有异常内部类。

上代码吧:

sales.h

#include <stdexcept>
#include <string>class Sales
{public:enum {MONTHS = 12};
// 声明内部异常类class bad_index: public std::logic_error{private:int bi;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;
};

sales.cpp

#include "sales.h"
#include <iostream>
using std::string;Sales::bad_index::bad_index(int ix, const string & s): std::logic_error(s), bi(ix)
{
}Sales::Sales(int yy)
{year = yy;for (int i = 0; i < MONTHS; ++i)gross[i] = 0;
}Sales::Sales(int yy, const double * gr, int n)
{year = yy;int lim = ( n < MONTHS) ? n : MONTHS;int i;for (i = 0; i < lim; ++i)gross[i] = gr[i];for (; i < MONTHS; ++i)gross[i] = 0;
}double Sales::operator[](int i) const
{if (i < 0 || i >= MONTHS)throw bad_index(i);return gross[i];
}double & Sales::operator[](int i)
{if (i < 0 || i >= MONTHS)throw bad_index(i);return gross[i];
}LabeledSales::nbad_index::nbad_index(const string & lb, int ix,const string & s) : Sales::bad_index(ix, s)
{lbl = lb;
}LabeledSales::LabeledSales(const string & lb, int yy) : Sales(yy)
{label = lb;
}LabeledSales::LabeledSales(const string & lb, int yy, const double * gr, int n): Sales(yy, gr, n)
{label = lb;
}double LabeledSales::operator[](int i) const
{if (i < 0 || i >= MONTHS)throw nbad_index(Label(), i);return Sales::operator[](i);
}double & LabeledSales::operator[](int i)
{if (i < 0 || i >= MONTHS)throw nbad_index(Label(), i);    return Sales::operator[](i);
}

user_sales.cpp

#include <iostream>
#include "sales.h"int main()
{using std::cout;using std::cin;using std::endl;double vals1[12] ={1220, 1100, 1122, 2212, 1232, 2334,1884, 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);cout << "First try block:\n";try{int i;cout << "Year = " << sales1.Year() << endl;cout << "Label = " << sales2.Label() << endl;
// 这里迭代的时候到12下标就溢出 这里会捕获到异常for ( i = 0; i <= 12; i++){cout << sales2[i] << " ";if (i % 6 == 5)cout << endl;}cout << "End of try block 1.\n";}catch(LabeledSales::nbad_index & bad){cout << bad.what();cout << "Company: " << bad.label_val() << endl;cout << "bad index: " << bad.bi_val() << endl;}catch(Sales::bad_index & bad){cout << bad.what();cout << "bad index: " << bad.bi_val() << endl;}cout << "done\n";return 0;
}

未捕获的异常和意外终止的异常

未捕获的异常系统会调用terminate()函数,该函数默认会调用abort()终止程序。

可以设置该函数使其执行自定义的函数

// terminate函数来源于exception
#include <exception>
using namespace std;// 设置terminate函数调用的是指定的函数
set_terminate(myQuit);void myQuit()
{
cout << "Terminating due to uncaught excetion\n";
exit(5);
}

意外发生的异常

可以指定异常贵方来指定捕获哪些异常:

// 声明抛出的异常类, 这是异常规范double Argh(double, double) throw(out_of_bounds);try{x = Argh(a, b);
}
// 在使用Argh函数的地方捕获指定异常
catch(out_of_bounds & ex)
{...
}

这样很麻烦 如果是在二开祖传代码 鬼知道会爆出什么过异常  如果都按照异常规范来操作的话 一来可能异常会有很多,这样写估计要写两大行。二来 咱这么详细 总会有漏 。所以 C++11开始要抛弃这种规范。

这种意外导致的异常C++是调用unexpected()函数(名字很奇怪) 然后该函数调用terminate() 然后该函数默认调用abort()

同样 也有set_unexpected()函数 用来自定义意外异常函数

但是此方法与set_terminate()不一样,有限制:

意思就是 set_unexpected()中如果是引发异常 那就看该异常与throw()中的异常是不是一样 如果是的话就就用该throw()中的异常处理,也就是说将意外情况引导到指定的catch中。

如果异常不在异常规范中,且规范中没有std::bad_exception类型的异常 则调用terminate()

如果异常不在异常规范中,但是规范中有std::bad_exception类型的异常。 那将会抛std::bad_exception异常

通用方法:

#include <exception>
using namespace std;set_unexpected(myUnexpected);void myUnexpected()
{// 或者 throw();throw std::bad_exception();
}double Argh(double, double) throw(out_of_bounds, bad_exception);
...
try{x = Argh(a, b);
}
catch(out_of_bounds & ex)
{...
}
catch(bad_exception & ex)
{...
}

异常注意事项

不要在模板中使用异常规范  因为涉及内存的动态分配问题

解决方案就是讲delete [] ar;反倒抛异常的语句前面

这说的是WINDOWS是么 - -

RTTI

运行阶段类型识别。 用来确定对象类型的

有三个元素

dynamic_cast:用来判断某个对象指针是否可以安全的转换为另一个对象的指针。如果可以,正常转换,如果不行,则返回空指针。格式:

Superb * pm = dynamic_cast<Superb *>(pg);

代码示例:

#include <iostream>
#include <cstdlib>
#include <ctime>using std::cout;class Grand
{private:int bold;public:Grand(int h = 0) : bold(h) {}virtual void Speak() const {cout << "I am a grand class!\n";}virtual int Value() const {return bold;}
};class Superb : public Grand
{public:Superb(int h = 0) : Grand(h) {}void Speak() const {cout << "I am superb class!!\n";}virtual void Say() const{ cout << "I hold the superb value of " << Value() << "!\n";}
};class Magnificent : public Superb
{private:char ch;public:Magnificent(int h = 0, char c = 'A') :Superb(h), ch(c) {}void Speak() const {cout << "I am a magnificent class!!!\n";}void Say() const {cout << "I hold the character " << ch<< " and the integer " << Value() << "!\n";}
};Grand * GetOne();int main()
{std::srand(std::time(0));Grand * pg;Superb * ps;for ( int i = 0; i < 5; i++){pg = GetOne();pg->Speak();if (ps = dynamic_cast<Superb *>(pg))ps->Say();}return 0;
}Grand * GetOne()
{Grand* p;switch( std::rand() % 3){case 0: p = new Grand(std::rand() % 100);break;case 1: p = new Superb(std::rand() % 100);break;case 2: p = new Magnificent(std::rand() % 100, 'A' + std::rand() % 26);break;}return p;
}

typeid运算符

判断两个对象是否同种类型

例子:

typeid(Magnificent) == typeid(*pg)(接收的参数可以是类名和结果为对象的表达式)

结果为true, 如果不同则返回false

如果pg是空指针 会引发bad_typeid异常。

type_info类:

程序示例

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <typeinfo>
using namespace std;class Grand
{private:int bold;public:Grand(int h = 0) : bold(h) {}virtual void Speak() const {cout << "I am a grand class!\n";}virtual int Value() const {return bold;}
};class Superb : public Grand
{public:Superb(int h = 0) : Grand(h) {}void Speak() const {cout << "I am superb class!!\n";}virtual void Say() const{ cout << "I hold the superb value of " << Value() << "!\n";}
};class Magnificent : public Superb
{private:char ch;public:Magnificent(int h = 0, char c = 'A') :Superb(h), ch(c) {}void Speak() const {cout << "I am a magnificent class!!!\n";}void Say() const {cout << "I hold the character " << ch<< " and the integer " << Value() << "!\n";}
};Grand * GetOne();int main()
{std::srand(std::time(0));Grand * pg;Superb * ps;for ( int i = 0; i < 5; i++){pg = GetOne();cout << "Now processing type " << typeid(*pg).name() << ".\n";pg->Speak();if(ps = dynamic_cast<Superb *>(pg))ps->Say();if (typeid(Magnificent) == typeid(*pg))cout << "Yes, you're really magnificent.\n";}return 0;
}Grand * GetOne()
{Grand* p;switch( std::rand() % 3){case 0: p = new Grand(std::rand() % 100);break;case 1: p = new Superb(std::rand() % 100);break;case 2: p = new Magnificent(std::rand() % 100, 'A' + std::rand() % 26);break;}return p;
}

类型转换运算符

为了更严格的定义类型转换

C++添加了以下4中运算符

dynamic_cast: 这个前面说过了 就是转换前判定是否能够安全转换对象类型

const_cast: 这个只用于一次性。除了将const转换成非const(或转成volatile类型)使用之外 转换成其他类型的都将抛出异常

                        这个只针对非const 传入到要求const的函数形参中 ,如果本身是const变量的话那是返回空指针

程序示例:

#include <iostream>
using std::cout;
using std::endl;
void change(const int * pt, int n);int main()
{int pop1 = 38383;const int pop2 = 2000;cout << "pop1, pop2: " << pop1 << ", " << pop2 << endl;change(&pop1, -103);change(&pop2, -103);cout << "pop1, pop2: " << pop1 << ", " << pop2 << endl;return 0;
}void change(const int * pt, int n)
{int* pc;pc = const_cast<int *>(pt);*pc += n;
}

运行结果

static_cast: 这个是检测用于被转换的类与将要转换成的类之间有没有关联  是不是基-派生 或者间接基类,间接派生 如果是两个完全不相干的类 则会抛异常

reinterpret_cast:这种是用于转换一些莫名其妙但是有时候又用得到的类型转换:看例子吧

正常不这么干吧 这样转换 好奇怪。

C++中转换的限制:

总结:

这篇关于C++ Primer Plus 学习笔记 第十五章 异常 abort() 返回错误提示,try-throw-catch exception和其派生类 RTTI 类型转换运算符的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/639634

相关文章

Windows下C++使用SQLitede的操作过程

《Windows下C++使用SQLitede的操作过程》本文介绍了Windows下C++使用SQLite的安装配置、CppSQLite库封装优势、核心功能(如数据库连接、事务管理)、跨平台支持及性能优... 目录Windows下C++使用SQLite1、安装2、代码示例CppSQLite:C++轻松操作SQ

电脑提示xlstat4.dll丢失怎么修复? xlstat4.dll文件丢失处理办法

《电脑提示xlstat4.dll丢失怎么修复?xlstat4.dll文件丢失处理办法》长时间使用电脑,大家多少都会遇到类似dll文件丢失的情况,不过,解决这一问题其实并不复杂,下面我们就来看看xls... 在Windows操作系统中,xlstat4.dll是一个重要的动态链接库文件,通常用于支持各种应用程序

C++中RAII资源获取即初始化

《C++中RAII资源获取即初始化》RAII通过构造/析构自动管理资源生命周期,确保安全释放,本文就来介绍一下C++中的RAII技术及其应用,具有一定的参考价值,感兴趣的可以了解一下... 目录一、核心原理与机制二、标准库中的RAII实现三、自定义RAII类设计原则四、常见应用场景1. 内存管理2. 文件操

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

Java对异常的认识与异常的处理小结

《Java对异常的认识与异常的处理小结》Java程序在运行时可能出现的错误或非正常情况称为异常,下面给大家介绍Java对异常的认识与异常的处理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参... 目录一、认识异常与异常类型。二、异常的处理三、总结 一、认识异常与异常类型。(1)简单定义-什么是

C++ 函数 strftime 和时间格式示例详解

《C++函数strftime和时间格式示例详解》strftime是C/C++标准库中用于格式化日期和时间的函数,定义在ctime头文件中,它将tm结构体中的时间信息转换为指定格式的字符串,是处理... 目录C++ 函数 strftipythonme 详解一、函数原型二、功能描述三、格式字符串说明四、返回值五

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

MyBatis Plus 中 update_time 字段自动填充失效的原因分析及解决方案(最新整理)

《MyBatisPlus中update_time字段自动填充失效的原因分析及解决方案(最新整理)》在使用MyBatisPlus时,通常我们会在数据库表中设置create_time和update... 目录前言一、问题现象二、原因分析三、总结:常见原因与解决方法对照表四、推荐写法前言在使用 MyBATis

Mybatis Plus Join使用方法示例详解

《MybatisPlusJoin使用方法示例详解》:本文主要介绍MybatisPlusJoin使用方法示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录1、pom文件2、yaml配置文件3、分页插件4、示例代码:5、测试代码6、和PageHelper结合6