您的位置 首页 php

C++try catch 异常捕获处理机制专题

前言:

异常是一种程序控制机制,与函数机制独立互补

函数是一种以栈为结构展开的上下函数衔接的程序控制系统.异常是另外一种控制结构,它依附于栈结构,却可以同时设置多个异常类型作为网捕条件,从而以类型匹配在栈机制中跳跃回馈

异常设计目的

栈机制是一种高度节律性机制,面向对象编程却要求对象之间有方向,有目的的控制传动,从一开始,异常就死冲着改变程序控制结构,以适应面向对象程序更有效地工作这个主题,而不是仅仅为了进行错误处理

异常设计出来以后却发现在错误处理上获得了最大的好处.

异常基本语法

1)若有异常则通过throw操作创建一个异常对象并抛掷.

2)将可能抛出异常的程序段嵌在try中,控制通过正常程序,然后执行try块内的保护段

3)如果在保护段执行期间没有引起异常,那么跟在try块后的catch字句就不执行.程序从try块后跟随的最后一个catch字句后面的语句继续执行下去

4)catch子句按其在try块后出现的顺序被检查,匹配的catch子句讲捕获并处理异常

5)如果匹配的处理器没有找到,则运行函数terminate将被自动调用,其缺省功能是调用abort终止程序.

6)处理不了的异常,可以在catch的最后一个分支,使用thorw语法,向上扔.

7)异常机制与函数机制互不干涩,但捕捉的方式是基于类型匹配,捕捉相当于函数返回类型的匹配,而不是函数参数的匹配,所以捕捉不用考虑一个抛掷中的多种数据类型匹配问题

异常处理的基本思想

1)C++的异常处理机制使得异常的引发和异常的处理不必在同一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理,上层调用者可以在适当的位置设计对不同类型异常的处理

2)异常是专门针对抽象编程中的一系列错误处理的,C++中不能借助函数机制,因为结构的本质是先进后出,依次访问,无法进行跳跃,但错误处理的特征却是遇到错误信息就想要转到若干上级以后再进行重新尝试.

3)异常超脱于函数机制,决定了其函数的跨越式回脉

4)异常跨越函数

异常接口声明

1)为了加强程序的可读性.可以在函数声明中列出可能抛出的所有异常类型

void func{}throw{A B C D};//这个函数func()能够且只能抛掷出类型A B C D 及其子类的异常

2)如果函数声明中没有包含任何异常接口声明,则函数可以抛掷任何类型的异常

3)一个不抛掷任何类型异常的函数可以声明为:void func{} throw{};

4)如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexpected函数会被调用,该函数默认行为调用terminate函数终止程序.

异常类型和异常变量的生命周期

1)throw的异常是有类型的,可以使.数字,字符串,类对象.

2)throw的异常是有类型的,catch严格按照类型进行匹配

3)注意异常对象的内存模型.

传统处理的例子

 #include<iostream>
using namespace std;
//传统处理
int my_strcpy(char*to, char *form)
{
if (form == NULL)
{
return 1;
}
if (to == NULL)
{
return 2;
}
while (*form != '\0')
{
*to = *form;
to++;
form++;
}
return 0;
}

void main()
{
int ret = 0;
char buf1[] = "asddff";
char buf2[1024] = { 0 };
ret = my_strcpy(buf2, buf1);
if (ret != 0)
{
switch (ret)
{
case1:
printf("源错\n");
break;
case2:
printf("目的错\n");
break;
case3:
printf("拷贝过程错\n");
break;
default:
            printf("未知错误\n");
break;
}
}
printf("buf2:%s\n", buf2);

cout << "hello..." << endl;
system("pause");
return;
}  

例子

 #include <iostream>  
#include <cstdio>
using namespace std;
int func3 (void) {
FILE* fp = fopen ("none", "r");//fopen失败会返回控指针NULL。
if (! fp)
return -1;
// ...
fclose (fp);
return 0;
}
int func2 (void) {
if (func3 () == -1)
return -1;
// ...
return 0;
}
int func1 (void) {
if (func2 () == -1)
return -1;
// ...
return 0;
}
int main (void) {
    //层层判断返回值
if (func1 () == -1) {    
cout << "执行失败!改天再见!" << endl;
return -1;
}
// ...
cout << "执行成功!恭喜恭喜!" << endl;
return 0;
}
  

例子

 #include <iostream>
#include <cstdio>
#include <csetjmp> //标c的函数,跳转
using namespace std;
jmp_buf g_env;     //jmp是专门为c量身定造的,有类的情况不适用,会跳转,因为不执行右括号,局部对象失去执行析构的机会,不会调用析构函数,会造成内存泄露
class A {
public:
A (void) {
cout << "A构造" << endl;
}
~A (void) {
cout << "A析构" << endl;
}
};
void func3 (void) {
A a;
FILE* fp = fopen ("none", "r");
if (! fp)
longjmp (g_env, -1);   //(没有定义类的时候)这个时候是的g_env变为-1,但是不在这返回,在main函数的setjmp处返回
// ...
fclose (fp);
}
void func2 (void) {
A a;
func3 ();
// ...
}
void func1 (void) {
A a;
func2 ();
// ...
}
int main (void) {
if (setjmp (g_env) == -1) {    //(没有定义类的时候)第一次到这,genv是0,所以执行下面的func1(),执行了后在fun3中的longjmp处在缓冲区使得g_env变为1,并在这使g_env返回
cout << "执行失败!改天再见!" << endl;
return -1;
}
func1 ();
// ...
cout << "执行成功!恭喜恭喜!" << endl;
return 0;
}
  

例子

 #include <iostream>
#include <cstdio>
using namespace std;
class A {
public:
A (void) {
cout << "A构造" << endl;
}
~A (void) {
cout << "A析构" << endl;
}
};
void func3 (void) {
A a;
FILE* fp = fopen ("none", "r");
if (! fp) {       //如果不发生异常,不执行throw,直接执行throw后面的语句
cout << "throw前" << endl;
throw -1;     //如果有异常,throw之后的语句不执行,直接右括号
cout << "throw后" << endl;
}
cout << "文件打开成功!" << endl;
// ...
fclose (fp);
}
void func2 (void) {
A a;
cout << "func3()前" << endl;
func3 ();     //如果有异常,则直接右括号
cout << "func3()后" << endl;
// ...
}
void func1 (void) {
A a;
cout << "func2()前" << endl;
func2 ();     //有异常,直接右括号
cout << "func2()后" << endl;
// ...
}
int main (void) {
try {
cout << "func1()前" << endl;
func1 ();      //之后进入func1,先创建a,执行构造,再进入func2,又创建a,执行构造,再进入func3,又创建a,执行构造,然后执行throw,抛出-1;结束func3,释放func3中的a,调用析构,然后func2结束,释放func2的a,调用析构,然后func1结束,释放func1的a,调用析构。然后直接到try的右花括号,然后执行异常处理,根据异常对象的类型匹配相应的catch,这里是“执行失败”。
cout << "func1()后" << endl;
}
catch (int ex) {
if (ex == -1) {
cout << "执行失败!改天再见!" << endl;
return -1;
}
}
// ...
cout << "执行成功!恭喜恭喜!" << endl;
return 0;
}
  

1.抛出基本类型的异常,用不同的值代表不同的错误。

例子:

 #include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
void foo (void) {
FILE* fp = fopen ("none", "r");
if (! fp)
throw "打开文件失败!";
void* pv = malloc (0xFFFFFFFF);
if (! pv)
throw "内存分配失败!";
// ...
}
int main (void) {
try {
foo (); //可能引发异常的语句快
}
catch (const char* ex) { //char类型的异常对象类型
cout << ex << endl;
return -1; //
} //有none文件,显示内存分配失败,没有none文件,显示打开文件失败
return 0;
}  

2.抛出类类型的异常,用不同的类型表示不同的错误。

例子:

 #include <iostream>/
#include <cstdio>
#include <cstdlib>
using namespace std;
class Error {};
class FileError : public Error {};
class MemError : public Error {};
void foo (void) {
FILE* fp = fopen ("none", "r");
if (! fp)
throw FileError (); //放到安全区
void* pv = malloc (0xFFFFFFFF);
if (! pv)
throw MemError ();
// ...
}
int main (void) {
try {
foo ();
}
catch (FileError& ex) { //用引用,效率高,避免拷贝构造
cout << "打开文件失败!" << endl;
return -1;
}
catch (MemError& ex) {//用引用
cout << "内存分配失败!" << endl;
return -1;
}
catch (Error& ex) { //如果放在最前面:会有警告,会捕获所有异常,一个子类的对象可以用基类的引用去引用,虽然后面有更适合的匹配,但是最先匹配原则
cout << "一般性错误!" << endl;
return -1;
}
return 0;
}  

——————————————————————–

3.通过类类型的异常携带更多诊断信息。

例子:

 #include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
class Error {
public:
virtual void print (void) const = 0;
};
class FileError : public Error {
public:
FileError (const string& file, int line) :
m_file (file), m_line (line) {}
void print (void) const {
cout << "在" << m_file << "文件的第"
<< m_line << "行,发生了文件错误!"
<< endl;
}
private:
string m_file;
int m_line;
};
class MemError : public Error {
public:
void print (void) const {
cout << "内存不够啦!!!" << endl;
}
};
void foo (void) {
FILE* fp = fopen ("none", "r");
if (! fp)
throw FileError (__FILE__, __LINE__); //这里抛出,所以不执行下面的语句,所以下面的异常没有抛出
void* pv = malloc (0xFFFFFFFF);
if (! pv)
throw MemError ();
// ...
}
int main (void) {
try {
foo ();
}
catch (Error& ex) {
ex.print ();
return -1;
}
return 0;
}
  

——————————————————————–

4.忽略异常和继续抛出异常。

例子:

 #include <iostream>
//继续抛出 记住用引用
using namespace std;
void foo (void) {
throw 10;
}
void bar (void) {
try {
foo ();
}
catch (int& ex) {
--ex; //安全区中的ex变为9
throw; // 继续抛出 抛出的ex为9.
}
// ...
}
int main (void) {
try {
bar ();
}
catch (int& ex) {
cout << ex << endl; // 9
}
return 0;
}  

——————————————————————–

5.异常说明

在一个函数的形参表后面写如下语句:

…形参表) throw (异常类型1, 异常类型2, …) { … }

表示这个函数可以被捕获的异常。

throw () – 这个函数所抛出的任何异常均无法捕获。

没有异常说明 – 这个函数所抛出的任何异常均可捕获。

 class A {
virtual void foo (void)
throw (int, double) { ... }
virtual void bar (void)
throw () { ... }
};
class B : public A {
void foo (void)
throw (int, char) { ... } // ERROR 不能比基类抛出更多的异常

void bar (void) { ... } // ERROR 不可以这样写覆盖函数
void bar (void)
throw () { ... }
};  

————————————————————-

例子:

 #include <iostream>
using namespace std;
void foo (void) throw (int, double, const char*){ //可以被捕获到的类型
// throw 1;
// throw 3.14;
throw "Hello, Exception !";
}
int main (void) {
try {
foo ();
}
catch (int ex) { //可以捕获int的异常
cout << ex << endl;
}
catch (double ex) { //可以捕获double的异常
cout << ex << endl;
}
catch (const char* ex) { //可捕获const char*的异常
cout << ex << endl;
// cout<<__LINE__<<endl;
}
return 0;
}
  

6.使用标准异常

#include <stdexcept>

———————————————————————

四、构造函数中的异常

构造函数可以抛出异常,而且有些时候还必须抛出异常,已通知调用者构造过程中所发生的错误。

如果在一个对象的构造过程中抛出了异常,那么这个对象就称为不完整对象。不完整对象的析构函数永远不会被指向,因此需要在throw之前,手动释放动态的资源。

例子:

 #include <iostream>
#include <stdexcept>
#include <cstdio>
//构造函数中的异常
using namespace std;
class FileError : public exception {
private:
const char* what (void) const throw () { //
return "文件访问失败!";
}
};
class B {
public:
B (void) {
cout << "B构造" << endl;
}
~B (void) {
cout << "B析构" << endl;
}
};
class C {
public:
C (void) {
cout << "C构造" << endl;
}
~C (void) {
cout << "C析构" << endl;
}
};
class A : public C {
public:
A (void) : m_b (new B) {
FILE* fp = fopen ("none", "r");
if (! fp) {
delete m_b; //再抛出异常前,将资源释放,因为一旦抛出异常,就不会执行析构函数。
throw FileError (); //会有回滚机制,所有调用的构造函数,会反向执行一遍析构函数,但除了动态变
}
// ...
fclose (fp);
}
~A (void) {
delete m_b;
}
private:
B* m_b;
// C m_c;
};
int main (void) {
try {
A a; //执行A的拷贝构造,
// ...
}
catch (exception& ex) {
cout << ex.what () << endl;//执行覆盖版本
return -1;
}
return 0;
}  

———————————————————————

五、析构函数中的异常

永远不要在析构函数中抛出异常。

 class A {
public:
~A (void) {
//throw -1; //错误,抛出后,直接到析构函数最后的花括号(1),然后直接到main函数中的花括号(2),花括号(2)结束,又调用此析构函数,形成死循环
try {

sysfunc ();
}
catch (...) {}
}(1)
};
try {
A a;
a.foo ();
} (2)
catch (...) { ... }
  

通过try-catch拦截所有可能引发的异常。

———————————————————————

六、C++的I/O流库

C:fopen/fclose/fread/fwrite/fprintf/fscanf/fseek/ftell…

C++:对基本的I/O操作做了类的封装,其功能没有任何差别,用法和C的I/O流也非常近似。

sscanf

七、格式化I/O

<< / >>

例子:

 #include <iostream>
#include <fstream>
//格式化I/O
using namespace std;
int main (void) {
//格式化的写
ofstream ofs ("format.txt"); //相当与c中的w,新加的内容会覆盖原有内容,打开这个文件,打开失败,ofs为false,成功为true
if (! ofs) {
perror ("打开文件失败");
return -1;
}
ofs << 1234 << ' ' << 56.78 << ' ' << "tarena"
<< '\n'; //将这写写入到文件里
ofs.close (); //关闭文件,如果不写也可以,结束后,调用ofstream析构函数会关闭掉

ofs.open ("format.txt", ios::app); //相当于c中的以a方式打开,可以在文件中追加,不会覆盖原有的内容
if (! ofs) {
perror ("打开文件失败");
return -1;
}
ofs << "append_a_line\n";
ofs.close ();

//格式化的读
ifstream ifs ("format.txt"); //要求文件必须存在,否则报错
if (! ifs) {
perror ("打开文件失败");
return -1;
}
int i;
double d;
string s1, s2;
ifs >> i >> d >> s1 >> s2; //读取文件中的内容到程序中。
cout << i << ' ' << d << ' ' << s1 << ' '
<< s2 << endl;
ifs.close (); //关闭文件
return 0;
}  

八、非格式化I/O

put / get

例子:

 #include <iostream>
#include <fstream>
//非格式化I/O
using namespace std;
int main (void) {
ofstream ofs ("putget.txt"); //定义一个ofstream类
if (! ofs) {
perror ("打开文件失败");
return -1;
}
for (char c = ' '; c <= '~'; ++c) //前加加返回的是一个引用,效率高,后加加返回的是拷贝,要进行一次拷贝,所以效率低。
if (! ofs.put (c)) { //ofs.put成功返回true,否则false。向文件中写入。
perror ("写入文件失败");
return -1;
}
ofs.close ();

ifstream ifs ("putget.txt");
if (! ifs) {
perror ("打开文件失败");
return -1;
}
char c;
while ((c = ifs.get ()) != EOF) //读如字符,直到返回EOF(表示读取完毕)。
cout << c;
cout << endl;
if (! ifs.eof ()) { //或者if(ifs.error())都可以判断是否出错
perror ("读取文件失败");
return -1;
}
ifs.close ();
return 0;
}  

九、随机I/O

seekp / seekg (p->put,g->get)

tellp / tellg

例子:

 #include <iostream>
#include <fstream>
//随机I/O
using namespace std;
int main (void) {
fstream fs ("seek.txt", ios::in | ios::out); //即可读也可写,相当于c中的r+,要求文件必须存在。
if (! fs) {
perror ("打开文件失败"); //打印错误信息。最近的一次错误的原因。
return -1;
}
fs << "0123456789"; //向文件里输入。
cout << fs.tellp () << endl; //获取写指针的位置,在下一个接受数据的位置,最后一个9所在的位置是9,所以写的位置为10
cout << fs.tellg () << endl; //获取读指针的位置,虽然没有读,但是会随着写指针一起走

//seekp()与seekg()函数分别有两个参数,第一个是偏移量,正负代表前后方向,第二个是从哪个位置开始
fs.seekp (-3, ios::cur); //调整写指针的位置,表示从当前位置往文件头移动三个字符
fs << "XYZ"; //覆盖789
fs.seekg (4, ios::beg); //调整读指针的位置,表示从文件头开始偏移四个位置
int i;
fs >> i; //从4开始读,读到6,后面是xyz所以不读,结束。
cout << i << endl;
cout << fs.tellg () << endl; //7
cout << fs.tellp () << endl; //7

fs.seekg (-6, ios::end); //从文件尾开始向文件头偏移6个位置
fs << "ABC";
fs.close ();
return 0;
}




十、二进制I/O
read / write
K 0 - 255
A^K=B
B^K=A
PKI
HAS MD5  

例子:加密文件(异或机制)

 #include <iostream>
#include <fstream>
#include <stdexcept>
#include <cstdlib>
using namespace std;
#define BUFSIZE (1024*10)
int _xor (const char* src, const char* dst,
unsigned char key) { //源文件,目标文件,密钥
ifstream ifs (src, ios::binary); //以二进制方式读
if (! ifs) {
perror ("打开源文件失败");
return -1;
}
ofstream ofs (dst, ios::binary);
if (! ofs) {
perror ("打开目标文件失败");
return -1;
}

char* buf = NULL; //创建缓冲区
try {
buf = new char[BUFSIZE];
}
catch (bad_alloc& ex) { //bad_alloc是标准库里的
cout << ex.what () << endl;
return -1;
}
while (ifs.read (buf, BUFSIZE)) { //缓冲区的地址,和大小
for (size_t i = 0; i < BUFSIZE; ++i)
buf[i] ^= key; //将每一个字符都与key异或
if (! ofs.write (buf, BUFSIZE)) { //以二进制方式写进去,缓冲区地址,和希望写的大小
perror ("写入文件失败");
return -1;
}
}
if (! ifs.eof ()) { //判断是否正常
perror ("读取文件失败");
return -1;
}

for (size_t i = 0; i < ifs.gcount (); ++i) //gcount函数返回剩下的大小
buf[i] ^= key; //将缓冲区中的剩下的也与key异或
if (! ofs.write (buf, ifs.gcount ())) {
perror ("写入文件失败");
return -1;
}
delete[] buf; //释放缓冲区
ofs.close (); //关闭文件
ifs.close (); //关闭文件
return 0;
}
int enc (const char* plain, const char* cipher) {
srand (time (NULL));
unsigned char key = rand () % 256; //0到256sui随机数
if (_xor (plain, cipher, key) == -1) //为-1则失败
return -1;
cout << "密钥:" << (unsigned int)key << endl; //告诉密钥是什么。转换为数的形式
return 0;
}
int dec (const char* cipher, const char* plain,
unsigned char key) {
return _xor (cipher, plain, key);
}
int main (int argc, char* argv[]) {
if (argc < 3) {
cerr << "用法:" << argv[0]
<< " <明文文件> <密文文件>" << endl;
cerr << "用法:" << argv[0]
<< " <密文文件> <明文文件> <密钥>"
<< endl;
return -1;
}
if (argc < 4)
return enc (argv[1], argv[2]);
else
return dec (argv[1], argv[2],
atoi (argv[3])); return 0; }
十一、格式控制
in a;
printf ("%d%x\n", a, a)
cout << a << endl;
流函数
流控制符
cout << hex << a;
cout << setw (10) << a;
  

例子:

 #include <iostream>
#include <iomanip> //要加这个头文件
#include <cmath> //数学库
#include <fstream> //文件头文件
#include <sstream> //字符串流
//格式控制
using namespace std;
int main (void) {
cout << sqrt (2) << endl; //求平方根,只输出六位有效数字,1.41421
cout.precision (10); //将精度设为10,10位有效数字
cout << sqrt (2) << endl; //输出十位。1.414213562
cout << sqrt (2) * 100 << endl; //141.4213562
cout << setprecision (5) << sqrt (2) << endl //将精度改为5,1.4152
<< sqrt (2) * 100 << endl; //141.42
cout << "当前精度:" << cout.precision () //可以返回当前精度
<< endl;
cout << setprecision (2) << 1.24 << ' ' << 1.25
<< ' ' << 1.26 << endl; //将精度设为2,1.2 1.2 1.3,只有大于5才会入,小于等于5都舍去。
cout << showbase << hex << 127 << endl; //打印进制标志,hex为十六进制
cout << oct << 127 << endl; //oct为8进制
cout << dec << 127 << endl; //dec为10进制
cout << noshowbase << hex << 127 << dec << endl; //关闭现实进制,并恢复为十进制,一次性起作用,输出紧跟着的后一个输出,后面的不会管。
cout << setw (12) << 127 << 721 << endl; //设置域宽,默认靠右,填充空格,只对127起作用,对721不起作用,一次性。
cout << setfill ('$') << left << setw (12) //用$填充,左对齐,域宽为12
<< 127 << endl;
cout.precision (10); //精度修改为10,对科学计数法和定点形式意义不一样
cout.setf (ios::scientific); //以科学计数法输出
cout << sqrt (2) << endl; //1.4142135624e+00//小数部位为十位
cout.setf (ios::fixed); //一定点小数形式输出(正常的)
cout << sqrt (2) << endl; //1.414213562 有效数字为十位
cout << 12.00 << endl; //12.00000000 因为前面精度设为10
cout << showpoint << 12.00 << endl; //显示小数点
cout << noshowpoint << 12.00 << endl; //不显示小数点

ifstream ifs ("stream.txt"); //打开文件
ifs.unsetf (ios::skipws); //取消跳过空白,否则默认将空格等制表符认为是分隔作用,不读取
char c;
while (ifs >> c)
cout << c;
ifs.setf (ios::skipws); //又设置回跳过空白
ifs.clear (); // 复位,将流复位,状态恢复到文件头,否则只改位置指针没有用
ifs.seekg (ios::beg); //此时位置指针不再文件头,这可以将位置指针返回文件头
while (ifs >> c)
cout << c;
ifs.close ();
cout << endl;

int i = 1234;
double d = 56.78;
string s = "tarena";
ostringstream oss; //定义输出字符串流对象
oss << i << ' ' << d << ' ' << s;
string str = oss.str (); //将字符串流流中的内容拿出来
cout << str << endl; //1234 56.78 tarena
str = "hello 3.14 pai";
istringstream iss; //定义输入字符串流对象
iss.str (str); //将str的内容读入字符串流
iss >> s >> d >> str;
cout << s << ' ' << d << ' ' << str << endl;
return 0;
}
  

文章来源:智云一二三科技

文章标题:C++try catch 异常捕获处理机制专题

文章地址:https://www.zhihuclub.com/77171.shtml

关于作者: 智云科技

热门文章

网站地图