大纲
类型转换
类型转换的分类
静态类型转换
static_cast
可以用于类层次结构中基类(父类)和派生类(子类)之间指针或者引用的转换。- 进行上行转换(将派生类的指针或者引用转换成基类表示)是安全的。
- 进行下行转换(将基类的指针或者引用转换成派生类表示),由于没有动态类型检查,所以是不安全的。
static_cast
可以用于基本数据类型之间的转换,比如将 int
转化成 char
,或者将 char
转换成 int
,这种类型转换的安全性需要开发人员来保证。
动态类型转换
dynamic_cast
可以用于类层次结构中的上行转换和下行转换,但是不支持基本数据类型的转换。- 在类层次结构中进行上行转换(将派生类的指针或者引用转换成基类表示)时,
dynamic_cast
与 static_cast
的效果一样。 - 在类层次结构中进行下行转换(将基类的指针或者引用转换成派生类表示)时,
dynamic_cast
具有类型检查的功能,比 static_cast
更安全。
常量类型转换
const_cast
可以用于赋予或者去除类型的 const
只读属性。- 常量指针被转换成非常量指针后,仍然指向原来的对象,反之亦然。
- 常量引用被转换成非常量引用后,仍然指向原来的对象,反之亦然。
特别注意
不能直接对非指针和非引用的类型使用 const_cast
操作符去直接赋予或者去除它的 const
只读属性。
重新解释类型
reinterpret_cast
用于不同类型之间进行强制类型转换,这是最不安全的一种类型转换机制,最可能出现问题,极少使用。reinterpret_cast
可以将一种数据类型强制转换成另一种数据类型,比如可以将一个指针转换成整数,也可以将整数转换成指针。
类型转换的语法
类型转换的使用
静态类型转换的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| #include <iostream>
using namespace std;
class Father {
public: Father(string name, int age) { this->m_Name = name; this->m_Age = age; }
virtual void print() { cout << endl << "name = " << this->m_Name << ", age = " << this->m_Age << endl; }
protected: string m_Name; int m_Age;
};
class Son : public Father {
public: Son(string name, int age, string hobby) : Father(name, age) { this->m_Name = name; this->m_Age = age; this->m_hobby = hobby; }
void print() override { cout << endl << "name = " << this->m_Name << ", age = " << this->m_Age << ", hobby = " << m_hobby << endl; }
private: string m_hobby;
};
void test01() { char a = 'a'; double d = static_cast<double>(a); cout << endl << d << endl; }
void test02() { Father father("Father", 60); Son son("Son", 25, "Game");
Father &father1 = static_cast<Father &>(son); father1.print();
Son &son2 = static_cast<Son &>(father); son2.print(); }
void test03() { Son *son1 = new Son("Son", 25, "Game"); Father *father1 = static_cast<Father *>(son1); father1->print();
Father *father2 = new Father("Father", 60); Son *son2 = static_cast<Son *>(father2); son2->print(); }
int main() { cout << endl << "-------- test01() --------"; test01();
cout << endl << "-------- test02() --------"; test02();
cout << endl << "-------- test03() --------"; test03(); return 0; }
|
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 9 10 11 12
| -------- test01() -------- 97
-------- test02() -------- name = Son, age = 25, hobby = Game
name = Father, age = 60
-------- test03() -------- name = Son, age = 25, hobby = Game
name = Father, age = 60
|
动态类型转换的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| #include <iostream>
using namespace std;
class Father {
public: Father(string name, int age) { this->m_Name = name; this->m_Age = age; }
virtual void print() { cout << endl << "name = " << this->m_Name << ", age = " << this->m_Age << endl; }
protected: string m_Name; int m_Age;
};
class Son : public Father {
public: Son(string name, int age, string hobby) : Father(name, age) { this->m_Name = name; this->m_Age = age; this->m_hobby = hobby; }
void print() override { cout << endl << "name = " << this->m_Name << ", age = " << this->m_Age << ", hobby = " << m_hobby << endl; }
private: string m_hobby;
};
void test01() { Father father("Father", 60); Son son("Son", 25, "Game");
Father &father1 = dynamic_cast<Father &>(son); father1.print();
}
void test02() { Son *son1 = new Son("Son", 25, "Game"); Father *father1 = dynamic_cast<Father *>(son1); father1->print();
Father *father2 = new Father("Father", 60); }
void test03() { Father *father = new Son("Son", 25, "Game");
Son *son = dynamic_cast<Son *> (father); son->print(); }
int main() { cout << endl << "-------- test01() --------"; test01();
cout << endl << "-------- test02() --------"; test02();
cout << endl << "-------- test03() --------"; test03();
return 0; }
|
程序运行输出的结果如下:
1 2 3 4 5 6 7 8
| -------- test01() -------- name = Son, age = 25, hobby = Game
-------- test02() -------- name = Son, age = 25, hobby = Game
-------- test03() -------- name = Son, age = 25, hobby = Game
|
常量类型转换的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include <iostream>
using namespace std;
void test01() { const int *p = nullptr; int *newP = const_cast<int *>(p);
int *p2 = nullptr; const int *newP2 = const_cast<const int *> (p2); }
void test02() { int age = 20; const int &ageRef = age; int &ageRef2 = const_cast<int &>(ageRef);
int num = 10; int &numRef = num; const int &numRef2 = const_cast<const int &>(numRef); }
void test03() { const int a = 10; }
int main() { test01(); test02(); test03(); return 0; }
|
重新解释类型的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <iostream>
using namespace std;
void test01() { int a = 10; int *p = reinterpret_cast<int *>(a); }
int main() { test01(); return 0; }
|
类型转换的综合使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
| #include <iostream>
using namespace std;
class Tree {
};
class Animal {
public: virtual void cry() = 0;
};
class Dog : public Animal {
public: void cry() override { cout << "dog cry ..." << endl; }
void watchHome() { cout << "dog watch home" << endl; }
};
class Cat : public Animal {
public: void cry() override { cout << "cat cry ..." << endl; }
void playBall() { cout << "cat play ball ..." << endl; }
};
void playAnimal(Animal *animal) { animal->cry(); Dog *dog = dynamic_cast<Dog *>(animal); if (dog != NULL) { dog->watchHome(); } Cat *cat = dynamic_cast<Cat *>(animal); if (cat != NULL) { cat->playBall(); } }
void printBuf(const char *buf) { char *m_buf = const_cast<char *>(buf); m_buf[0] = 'b'; cout << buf << endl; cout << m_buf << endl; }
void printBuf2() { char* buf = "aaaaa"; char* m_buf = const_cast<char*>(buf); m_buf[0] = 'b'; cout << buf << endl; cout << m_buf << endl; }
int main() { char *p1 = "hello"; double pi = 3.1415926;
int num1 = static_cast<int>(pi); cout << "num1 = " << num1 << endl;
int *p2 = reinterpret_cast<int *>(p1); cout << "p2 = " << p2 << endl;
char buf[] = "aaaaa"; printBuf(buf);
Dog dog; Cat cat; playAnimal(&dog); playAnimal(&cat);
Animal *pAnimal = NULL; pAnimal = &dog; pAnimal = static_cast<Animal *>(&dog); pAnimal->cry(); pAnimal = reinterpret_cast<Animal *>(&dog); pAnimal->cry();
Tree tree; pAnimal = reinterpret_cast<Animal *>(&tree);
return 0; }
|
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 9 10
| num1 = 3 p2 = 005661B8 baaaa baaaa dog cry ... dog watch home cat cry ... cat play ball ... dog cry ... dog cry ...
|
使用总结:
- 一般情况下,不建议进行类型转换,应该避免进行类型转换
- 要清楚地知道:要转换的变量,类型转换前是什么类型,类型转换后是什么类型,转换后有什么后果
类型转换的总结
类型转换的概述:
- (a)
const_cast<>()
:常量类型转换,用于赋予或者去除类型的 const
只读属性 - (b)
reinterpret_cast<>()
:重新解释类型,不同类型之间会进行强制类型转换 - (c)
dynamic_cast<>()
:动态类型转换,安全的基类和派生类之间转换,运行时会做类型检查 - (d)
static_cast<>()
:静态类型转换,编译的时候 C++ 编译器会做类型检查,基本类型都能转换,但是不能转换指针类型(多态除外)
类型转换的总结:
- (a) 在 C 语言中,不能隐式类型转换的,在 C++ 中可以用
reinterpret_cast<>()
进行强行类型解释 - (b) 在 C 语言中,能隐式类型转换的,在 C++ 中可用
static_cast<>()
进行类型转换,因为 C++ 编译器在编译的时候,一般都可以顺利通过类型检查 - (c)
static_cast<>()
和 reinterpret_cast<>()
基本上把 C 语言中的强制类型转换功能给覆盖了,但 reinterpret_cast<>()
很难保证代码的移植性
异常处理机制
Bjarne Stroustrup 说:提供异常的基本目的就是为了处理程序运行期间出现的问题。基本思想是让一个函数在出现了自己无法处理的错误时抛出(throw)一个异常,然后它的(直接或者间接)调用者能够处理这个问题。这也就是《C++ Primer》中说的:将问题检测和问题处理相分离。在所有支持异常处理的编程语言中(如 Java),要认识到的一个思想:在异常处理过程中,由问题检测代码可以抛出一个对象给问题处理代码,通过这个对象的类型和内容,实际上完成了两个部分的通信,通信的内容是 “出现了什么错误”,当然,各种语言对异常的具体实现有着或多或少的区别,但是这个通信的思想是不变的。一句话概括:异常处理就是处理程序中的错误。所谓错误是指在程序运行的过程中发生的一些异常事件(如:除 0 溢出、数组下标越界、所要读取的文件不存在、空指针、内存不足等等)。
异常处理的简单介绍
- 异常的概述:
- 异常是一种程序控制机制,与函数机制独立和互补。
- 函数是一种以栈结构展开的上下函数衔接的程序控制系统,而异常是另一种控制结构,它依附于栈结构,却可以同时设置多个异常类型作为捕获条件,从而实现以类型匹配在栈机制中跳跃回馈。
- 异常设计目的:
- 异常设计出来之后,却发现在错误处理方面获得了最大的好处。
- 栈机制是一种高度节律性的控制机制,面向对象编程却要求对象之间有方向、有目的的控制传动,从一开始,异常就是冲着改变程序控制结构,以适应面向对象程序更有效地工作这个主题,而不是仅为了进行错误处理。
异常处理的基本思想
传统错误处理机制
在 C 语言的世界中,对错误的处理总是围绕着两种方法:一是使用整型的返回值标识错误;二是使用 emo
宏(可以简单的理解为一个全局整型变量)去记录错误。当然,C++ 中仍然是可以使用这两种方法的。这两种方法最大的缺陷就是会出现不一致的问题。例如,有些函数返回 1 表示成功,返回 0 表示出错;而有些函数返回 0 表示成功,返回非 0 表示出错。还有一个缺点就是函数的返回值只有一个,通过函数的返回值表示错误代码,那么函数就不能返回其他的值。当然,也可以通过指针或者 C++ 的引用来返回另外的值,但是这样可能会令程序略微晦涩难懂。
异常处理的基本思想
- 异常跨越了函数,并超脱于函数机制,决定了其对函数的跨越式回跳。
- C++ 的异常处理机制使得异常的引发和异常的处理不必在同一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理,上层调用者可以在适当的位置设计对不同类型异常的处理。
- 异常是专门针对抽象编程中的一系列错误进行处理的,C++ 中不能借助函数机制,因为栈结构的本质是先进后出,依次访问,无法进行跳跃,但错误处理的特征却是遇到错误信息就想要转到若干级之上进行重新尝试,如图所示:
C++ 异常的基础使用
异常的基本使用语法
- (a) 若有异常,则可以通过
throw
操作符创建一个异常对象并抛出。 - (b) 将可能抛出异常的程序段嵌在
try
块之中,控制通过正常的顺序执行到达 try
语句,然后执行 try
代码块内的保护段。 - (c) 如果在保护段执行期间没有引起异常,那么跟在
try
代码块后的 catch
子句就不会执行,程序从 try
代码块后跟随的最后一个 catch
子句后面的语句将继续执行下去。 - (d)
catch
子句按其在 try
代码块后出现的顺序被检查,匹配到的 catch
子句将捕获并处理异常(或继续抛掷异常)。 - (e) 如果匹配的异常处理器未被找到,则函数
terminate()
将被自动调用,其缺省功能是调用函数 abort()
终止程序的运行。 - (f) 处理不了的异常,可以在
catch
子句的最后一个分支,使用 throw
语法,向上抛掷异常。
异常的基础使用案例一
这里将演示在 C++ 中,如何抛出异常和捕获异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include <iostream>
using namespace std;
int devide(int a, int b) { if (b == 0) { throw -1;
} return a / b; }
int main() { try { int a = 10; int b = 0; int c = devide(a, b); cout << "result: " << c << endl; } catch (int) { cout << "int 类型异常捕获,被除数不能为零" << endl; } catch (double) { cout << "double 类型异常捕获,被除数不能为零" << endl; } catch (...) { throw "发生未知的异常 ..."; }
cout << "程序正常结束运行" << endl; return 0; }
|
程序运行输出的结果如下:
1 2
| int 类型异常捕获,被除数不能为零 程序正常结束运行
|
异常的基础使用案例二
这里将演示在 C++ 中,如何抛出自定义异常和捕获自定义异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include <iostream>
using namespace std;
class MyException {
public: MyException(string message) { this->message = message; }
void printError() { cout << "Exception: " << this->message << endl; }
private: string message;
};
int devide(int a, int b) { if (b == 0) { throw MyException("被除数不能为零"); } return a / b; }
int main() { try { int result = devide(9, 0); cout << "result = " << result << endl; } catch (MyException e) { e.printError(); }
cout << "程序正常结束运行" << endl; return 0; }
|
程序运行输出的结果如下:
1 2
| Exception: 被除数不能为零 程序正常结束运行
|
异常的基础使用案例三
异常机制与函数机制互不干涉,但捕捉的方式是基于类型匹配。异常捕捉相当于函数返回类型的匹配,而不是函数参数的匹配,所以异常捕捉不用考虑一个抛掷中的多种数据类型匹配问题。C++ 的异常捕捉是严格按照类型匹配的,它的类型匹配之苛刻程度可以和模板的类型匹配相媲美。它不允许相容类型的隐式转换,比如,抛掷 char
类型的异常,用 int
类型就捕捉不到对应的异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| #include <iostream>
using namespace std;
class A { };
class B { };
int main() { try { int a; int i = 0; double d = 2.3; char chars[20] = "Hello"; string str = "World";
cout << "Please input a exception number: "; cin >> a;
switch (a) { case 1: throw i; case 2: throw d; case 3: throw chars; case 4: throw str; case 5: throw A(); case 6: throw B(); default: cout << "No exception throws here.\n"; } } catch (int) { cout << "int exception.\n"; } catch (double) { cout << "double exception.\n"; } catch (char *) { cout << "char* exception.\n"; } catch (string) { cout << "string exception.\n"; } catch (A) { cout << "class A exception.\n"; } catch (B) { cout << "class B exception.\n"; }
cout << "That's ok.\n"; return 0; }
|
程序运行输出的结果如下:
1 2 3
| Please input a exception number: 3 char* exception. That's ok.
|
C++ 异常的进阶使用
栈解旋
异常被抛出后,从进入 try
代码块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构,而析构的顺序与构造的顺序相反,这一过程就称为 栈解旋(Unwinding)
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include <iostream>
using namespace std;
class Test {
public: Test(int a, int b) { this->a = a; this->b = b; cout << "构造函数被调用" << endl; }
~Test() { cout << "析构函数被调用" << endl; }
private: int a; int b; };
int divide(int x, int y) { Test t1(3, 4), t2(5, 6); if (0 == y) { throw y; } return x / y; }
int main() {
try { int result = divide(5, 0); cout << "result = " << result << endl; } catch (int e) { cout << e << ", 被除数不能为零" << endl; } catch (...) { cout << "发生未知的异常"; } return 0; }
|
程序运行输出的结果如下:
1 2 3 4 5
| 构造函数被调用 构造函数被调用 析构函数被调用 析构函数被调用 0, 被除数不能为零
|
异常接口的声明
- (a) 为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型,例如:
void func() throw (A, B, C , D) {}
,这个函数 func()
能够且只能抛出类型 A、B、C、D 及其子类型的异常。 - (b) 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,
unexpected()
函数会被调用,该函数的默认行为是调用 terminate()
函数中止程序。 - (c) 一个不抛掷任何类型异常的函数,可以声明为:
void func() throw() {}
。使用这种写法时,如果函数内部抛出了异常,那么程序会终止运行。 - (d) 如果在函数声明中没有包含异常接口声明,则此函数可以抛掷任何类型的异常,例如:
void func() {}
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include <iostream>
using namespace std;
class A {
};
class B {
};
class C {
};
class D {
};
void func1() throw(A, B, C, D) { throw A(); }
void func2() throw() {
}
void func3() { throw B(); }
int main() { try { func1(); } catch (A a) { cout << "发生 A 异常" << endl; } catch (...) { cout << "发生未知其他异常 ..." << endl; } return 0; }
|
程序运行输出的结果如下:
异常的多态使用
使用案例一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <iostream>
using namespace std;
class BaseException {
public: virtual void printError() { cout << "Base Exception" << endl; }
};
class NullPointerException : public BaseException {
void printError() override { cout << "Null Pointer Exception" << endl; }
};
void doWrok() { throw NullPointerException(); }
int main() { try { doWrok(); } catch (BaseException &e) { e.printError(); } return 0; }
|
程序运行输出的结果如下:
使用案例二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #pragma once
#include <iostream>
using namespace std;
class SizeException {
public: virtual void printErr() = 0;
public: int getSize() { return this->size; }
protected: int size = 0; };
class NegativeException : public SizeException {
public: NegativeException(int size) { this->size = size; }
void printErr() { cout << "数组大小不能小于零, 当前大小为 " << this->size << endl; }
};
class TooBigException : public SizeException {
public: TooBigException(int size) { this->size = size; }
void printErr() { cout << "数组大小太大, 当前大小为 " << this->size << endl; }
};
class ZeroException : public SizeException {
public: ZeroException(int size) { this->size = size; }
void printErr() { cout << "数组大小不允许为零" << endl; } };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| #pragma once
#include <iostream> #include "MyException.h"
using namespace std;
class MyArray {
public: MyArray(int size) { if (size < 0) { throw NegativeException(size); } else if (size == 0) { throw ZeroException(size); } else if (size > this->m_max_size) { throw TooBigException(size); } this->m_size = size; this->m_space = new int[size]; }
MyArray(const MyArray& obj) { this->m_size = obj.m_size; this->m_space = new int[obj.m_size]; for (int i = 0; i < obj.m_size; i++) { this->m_space[i] = obj.m_space[i]; } }
~MyArray() { if (this->m_space) { delete[] this->m_space; this->m_space = NULL; this->m_size = 0; } }
public: int& operator[](int index) { return this->m_space[index]; }
MyArray& operator=(const MyArray& obj) { if (this->m_space) { delete[] this->m_space; this->m_space = NULL; this->m_size = 0; } this->m_size = obj.m_size; this->m_space = new int[obj.m_size]; for (int i = 0; i < obj.m_size; i++) { this->m_space[i] = obj.m_space[i]; } return *this; }
friend ostream& operator<<(ostream& out, const MyArray& obj);
public: int getsize() { return m_size; }
private: int* m_space; int m_size; int m_max_size = 1000; };
ostream& operator<<(ostream& out, const MyArray& obj) { for (int i = 0; i < obj.m_size; i++) { out << obj.m_space[i] << ", "; } return out; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include "MyArray.h"
int main() { try { MyArray array1(-6);
for (int i = 0; i < array1.getsize(); i++) { array1[i] = 20 + i; }
cout << array1 << endl;
MyArray array2 = array1; cout << array2 << endl;
MyArray array3(3); array3[0] = 43; array3[1] = 56; array3[2] = 79; cout << array3 << endl;
array3 = array2; cout << array3 << endl;
} catch (SizeException& e) { e.printErr(); } catch (...) { cout << "发生未知异常" << endl; } return 0; }
|
程序运行输出的结果如下:
异常变量的生命周期
throw
的异常是有类型的,可以使用数字、字符串、类对象,catch
会严格按照类型进行匹配。throw
出类对象类型的异常时:- 如果捕获异常的时候,使用一个异常变量,则拷贝构造该异常变量。
- 如果捕获异常的时候,使用了引用,则会直接使用
throw
时候的那个对象。 - 捕获异常的时候,指针可以和引用 / 元素同时出现,但是引用与元素不能同时出现。
- 结论:如果抛出的是类对象类型的异常,则使用引用进行异常捕获比较合适。
异常变量的使用案例一
如果捕获异常的时候,直接使用一个异常变量,则会拷贝构造该异常变量。也就是说,会调用自定义异常类的拷贝构造函数,即会多一份数据开销。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include <iostream>
using namespace std;
class MyException {
public:
MyException() { cout << "调用构造函数" << endl; }
MyException(const MyException &e) { cout << "调用拷贝构造函数" << endl; }
~MyException() { cout << "调用析构函数" << endl; }
};
void doWork() { throw MyException(); }
int main() { try { doWork(); } catch (MyException e) { cout << "捕获到自定义异常 ..." << endl; } catch (...) { cout << "捕获到未知异常 ..." << endl; } return 0; }
|
程序运行输出的结果如下:
1 2 3 4 5
| 调用构造函数 调用拷贝构造函数 捕获到自定义异常 ... 调用析构函数 调用析构函数
|
异常变量的使用案例二
如果捕获异常的时候,使用了引用,则会使用 throw 时候的那个对象。也就是说,捕获异常时使用引用,不会调用自定义异常类的拷贝构造函数,即会少一份数据开销。强烈推荐以后都使用引用这种方式来进行异常捕获,因为可以减少内存开销。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include <iostream>
using namespace std;
class MyException {
public:
MyException() { cout << "调用构造函数" << endl; }
MyException(const MyException &e) { cout << "调用拷贝构造函数" << endl; }
~MyException() { cout << "调用析构函数" << endl; }
};
void doWork() { throw MyException(); }
int main() { try { doWork(); } catch (MyException &e) { cout << "捕获到自定义异常 ..." << endl; } catch (...) { cout << "捕获到未知异常 ..." << endl; }
return 0; }
|
程序运行输出的结果如下:
1 2 3
| 调用构造函数 捕获到自定义异常 ... 调用析构函数
|
异常变量的使用案例三
如果捕获异常的时候,使用了指针,那么尽量不要直接 throw 一个内存地址,否则在 catch
代码块内不能再使用对应的指针。因为在执行 catch
代码块里面的代码之前,对应的指针已经被释放(变成了野指针);如果继续使用对应的指针,虽然程序可能不会立刻终止掉,但是会存在极大的内存安全问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #include <iostream>
using namespace std;
class MyException {
public:
MyException() { cout << "调用构造函数" << endl; }
MyException(const MyException &e) { cout << "调用拷贝构造函数" << endl; }
~MyException() { cout << "调用析构函数" << endl; }
void printError() { cout << "Exception Message: null" << endl; }
};
void doWork() { MyException exception; throw &exception; }
int main() { try { doWork(); } catch (MyException *e) {
cout << "捕获到自定义异常 ..." << endl; } catch (...) { cout << "捕获到未知异常 ..." << endl; }
return 0; }
|
程序运行输出的结果如下:
1 2 3
| 调用构造函数 调用析构函数 捕获到自定义异常 ...
|
异常变量的使用案例四
如果捕获异常的时候,使用了指针,那么尽量 throw 一个通过 new
操作符创建出来的指针,然后在 catch
代码块内使用完对应的指针后,手动对该指针执行 delete
操作,这样才会释放该指针指向的内存空间(比如会自动调用自定义异常类的析构函数来释放内存)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include <iostream>
using namespace std;
class MyException {
public:
MyException() { cout << "调用构造函数" << endl; }
MyException(const MyException &e) { cout << "调用拷贝构造函数" << endl; }
~MyException() { cout << "调用析构函数" << endl; }
void printError() { cout << "Exception Message: null" << endl; }
};
void doWork() { throw new MyException(); }
int main() { try { doWork(); } catch (MyException *e) {
e->printError();
delete e;
cout << "捕获到自定义异常 ..." << endl; } catch (...) { cout << "捕获到未知异常 ..." << endl; }
return 0; }
|
程序运行输出的结果如下:
1 2 3 4
| 调用构造函数 Exception Message: null 调用析构函数 捕获到自定义异常 ...
|
C++ 提供的标准异常类
标准异常类的介绍
标准异常类的使用
基础使用案例一
这里将演示如何直接使用 C++ 提供的标准异常类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <iostream> #include <stdexcept>
using namespace std;
class Person {
public: Person(string name, int age) { if (age < 0 || age > 150) { throw out_of_range("年龄超出范围"); }
if (name.length() <= 0 || name.length() > 32) { throw length_error("姓名长度错误"); }
this->m_Name = name; this->m_Age = age; }
private: string m_Name; int m_Age;
};
int main() { try { Person person("Tom", 200); } catch (out_of_range &e) { cout << e.what() << endl; } catch (length_error &e) { cout << e.what() << endl; } catch (...) { cout << "发生未知类型的异常!" << endl; }
return 0; }
|
程序运行输出的结果如下:
基础使用案例二
这里将演示如何继承 C++ 的标准异常类,以此来定义自己的异常类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #include <iostream> #include <stdexcept>
using namespace std;
class MyOutOfRangeException : public exception {
public: explicit MyOutOfRangeException(const string &str) { this->m_Error = str.c_str(); }
virtual ~MyOutOfRangeException() {
}
virtual const char *what() { return this->m_Error; }
private: const char *m_Error;
};
class Person {
public: Person(string name, int age) { if (age < 0 || age > 150) { throw MyOutOfRangeException("发生自定义异常:年龄超出范围"); }
this->m_Name = name; this->m_Age = age; }
private: string m_Name; int m_Age;
};
int main() { try { Person person("Tom", 200); } catch (MyOutOfRangeException &e) { cout << e.what() << endl; } catch (...) { cout << "发生未知类型的异常!" << endl; }
return 0; }
|
程序运行输出的结果如下:
C++ 默认的异常处理器
terminate () 函数
在 C++ 中,异常是不可以忽略的,当异常找不到匹配的 catch
子句时,会调用系统的库函数 terminate()
(在头文件中);默认情况下,terminate()
函数会调用标准 C 库函数 abort()
使程序终止而退出。当调用 abort()
函数时,程序不会调用正常的终止函数,也就是说,全局对象和静态对象的析构函数不会执行,这就可能会导致内存泄漏。值得一提的是,在多线程程序中,各个 terminate()
函数是互相独立的,每个线程都有自己的 terminate()
函数。
set_terminate () 函数
在 C++ 中,通过使用标准的 set_terminate()
函数,可以设置自己的 terminate()
函数。自定义的 terminate()
函数不能有参数,而且返回值类型必须为 void
。另外,terminate()
函数不能抛出异常,它必须终止程序。如果 terminate()
函数被调用,这就意味着问题已经无法解决了。
设置默认的异常处理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include <iostream>
using namespace std;
void myTerminate() { cout << "函数 myTerminate() 被 terminate() 调用!" << endl; exit(-1); }
int divide(int x, int y) { return x / y; }
int main() { set_terminate(myTerminate);
int x = 10, y = 0, result; try { if (y == 0) { throw "被除数为零!"; } else { result = x / y; } } catch (int e) { cout << "捕获到整型异常!" << endl; }
cout << "程序正常结束运行!" << endl; return 0; }
|
程序运行输出的结果如下:
1
| 函数 myTerminate() 被 terminate() 调用!
|