大纲 类型转换 类型转换的语法 C 语言风格的强制类型转换(Type Cast)很简单,不管什么类型的转换,语法都是:TYPE b = (TYPE) a
C++ 风格的类型转换,提供了 4 种类型转换操作符来应对不同场合的应用
const_cast
:去除变量的 const
只读属性reinterpreter_cast
:重新解释类型(强制类型转换)static_cast
:静态类型转换,如 int
转换成 char
dynamic_cast
:动态类型转换,如父类和子类之间的多态类型转换 C++ 4 种类型转换的语法:TYPE B = static_cast<TYPE> (a)
类型转换的一般性介绍 一般性介绍:
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<>()
很难保证代码的移植性 类型转换的简单使用案例 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 ...
使用总结:
一般情况下,不建议进行类型转换,应该避免进行类型转换 要清楚地知道:要转换的变量,类型转换前是什么类型,类型转换后是什么类型,转换后有什么后果 异常处理机制 异常的介绍:异常是一种程序控制机制,与函数机制独立和互补 函数是一种以栈结构展开的上下函数衔接的程序控制系统,而异常是另一种控制结构,它依附于栈结构,却可以同时设置多个异常类型作为捕获条件,从而实现以类型匹配在栈机制中跳跃回馈 异常设计目的:栈机制是一种高度节律性的控制机制,面向对象编程却要求对象之间有方向、有目的的控制传动,从一开始,异常就是冲着改变程序控制结构,以适应面向对象程序更有效地工作这个主题,而不是仅为了进行错误处理 异常设计出来之后,却发现在错误处理方面获得了最大的好处 异常处理的基本思想 传统错误处理机制 传统的程序错误处理机制,是通过函数返回值来处理错误。
异常处理的基本思想
异常跨越了函数,并超脱于函数机制,决定了其对函数的跨越式回跳 C++ 的异常处理机制使得异常的引发和异常的处理不必在同一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理,上层调用者可以在适当的位置设计对不同类型异常的处理 异常是专门针对抽象编程中的一系列错误进行处理的,C++ 中不能借助函数机制,因为栈结构的本质是先进后出,依次访问,无法进行跳跃,但错误处理的特征却是遇到错误信息就想要转到若干级之上进行重新尝试,如图所示:
C++ 异常的基础使用 异常的基本语法
a) 若有异常则通过 throw
操作创建一个异常对象并抛掷 b) 将可能抛出异常的程序段嵌在 try
块之中,控制通过正常的顺序执行到达 try
语句,然后执行 try
代码块内的保护段 c) 如果在保护段执行期间没有引起异常,那么跟在 try
代码块后的 catch
子句就不会执行,程序从 try
代码块后跟随的最后一个 catch
子句后面的语句将继续执行下去 d) catch
子句按其在 try
代码块后出现的顺序被检查,匹配到的 catch
子句将捕获并处理异常(或继续抛掷异常) e) 如果匹配的异常处理器未被找到,则函数 terminate()
将被自动调用,其缺省功能是调用函数 abort()
终止程序的运行 f) 处理不了的异常,可以在 catch
子句的最后一个分支,使用 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 #include <iostream> using namespace std;int divide (int x, int y) { 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 (...) { throw "发生未知的异常 ..." ; } cout << "程序正常结束运行" << endl; return 0 ; }
程序运行输出的结果如下:
异常的简单使用案例二 异常机制与函数机制互不干涉,但捕捉的方式是基于类型匹配。异常捕捉相当于函数返回类型的匹配,而不是函数参数的匹配,所以异常捕捉不用考虑一个抛掷中的多种数据类型匹配问题。异常捕捉是严格按照类型匹配的,它的类型匹配之苛刻程度可以和模板的类型匹配相媲美。它不允许相容类型的隐式转换,比如,抛掷 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 #include <iostream> using namespace std;class A { };class B { };int main () { try { int a; int i = 0 ; double d = 2.3 ; char str[20 ] = "Hello" ; cout << "Please input a exception number: " ; cin >> a; switch (a) { case 1 : throw i; case 2 : throw d; case 3 : throw str; case 4 : throw A (); case 5 : 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 (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.
异常在继承中的使用案例 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 ; }
程序运行输出的结果如下:
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 #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() {}
d) 一个不抛掷任何类型异常的函数,可以声明为:void func() 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 #include <iostream> using namespace std;class A { };class B { };class C { };class D { };class F { };void funcA () throw (A, B, C, D) { throw A (); } void funcB () throw () {} void funcC () { throw B (); } int main () { try { funcA (); } catch (...) { cout << "发生异常 ..." << endl; } return 0 ; }
程序运行输出的结果如下:
默认的异常处理器 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() 调用!
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 #include <iostream> using namespace std;class Teacher {public : Teacher (int age) { if (age > 100 ) { throw out_of_range ("年龄太大" ); } this ->age = age; } private : int age; }; class MyException : public exception {public : MyException (const char *p) { this ->m_p = p; } virtual const char *what () { cout << "MyException 类型的异常 : " << m_p << endl; return m_p; } private : const char *m_p; }; int main () { try { throw MyException ("发生自定义异常!" ); } catch (out_of_range e) { cout << "out_of_range 类型的异常 : " << e.what () << endl; } catch (MyException &e) { e.what (); } catch (...) { cout << "发生未知类型的异常!" << endl; } return 0 ; }
程序运行输出的结果如下:
1 MyException 类型的异常 : 发生自定义异常!
异常类型和异常变量的生命周期 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 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 #include <iostream> using namespace std;class BadSrcType {}; class BadDestType {}; class BadProcessType {public : BadProcessType () { cout << "BadProcessType的构造函数被调用" << endl; } BadProcessType (const BadProcessType& obj) { cout << "BadProcessType的拷贝构造函数被调用" << endl; } ~BadProcessType () { cout << "BadProcessType的析构函数被调用" << endl; } }; void myStrcpy (char * to, char * from) { if (to == NULL ) { throw BadDestType (); } if (from == NULL ) { throw BadSrcType (); } if (*from == 'a' ) { throw BadProcessType (); } if (*from == 'b' ) { throw & (BadProcessType ()); } if (*from == 'c' ) { throw new BadProcessType; } while (*from != '\0' ) { *to = *from; to++; from++; } *to = '\0' ; } int main () { int ret = 0 ; char buf1[] = "cbbcdefg" ; char buf2[1024 ] = { 0 }; try { myStrcpy (buf2, buf1); } catch (BadSrcType e) { cout << " BadSrcType 类型异常" << endl; } catch (BadDestType e) { cout << " BadDestType 类型异常" << endl; } catch (...) { cout << "未知 类型异常" << endl; } return 0 ; }