在 C {\rm C} C++中存在四种形式的强制类型转换:static_cast
,interpret_cast
,const_cast
和dynamic_cast
。
用来进行比较自然和低风险的转换,比如整型和实数型、字符型之间相互转换等。static_cast
不能在不同类型的指针之间相互转换,也不能用于整型和指针之间的相互转换,也不能用于不同类型的引用之间的相互转换。示例程序:
#include
using namespace std;
class A {
public:
// 重载int(),并返回1
operator int() {
return 1;
}
// 重载char*(),并返回NULL
operator char* () {
return NULL;
}
};
int main() {
A a;
int n; char* p = (char*)"New Good.";
// 将3.14强制转换为3
n = static_cast<int>(3.14);
// 将a对象转化成int,调用int(),n的值变为1
n = static_cast<int>(a);
// p的值变成NULL
p = static_cast<char*>(a);
// 编译错误,static_cast不能将指针转换成整型
n = static_cast<int>(p);
// 编译错误,static_cast不能将整型转换成指针
p = static_cast<char*>(n);
return 0;
}
用来进行各种不同类型的指针之间的转换,不同类型的引用之间的转换,指针和能容纳得下指针的整数类型之间的转换。转换的时候,执行的是逐个比特拷贝的操作。示例程序:
#include
using namespace std;
class A {
public:
int i; int j;
A(int n):i(n),j(n){} // 初始化列表
};
int main() {
A a(100);
// 强行让r引用a
int& r = reinterpret_cast<int&>(a);
// r引用的内容变成200,r引用a前四个字节,即i的值
r = 200;
// 200 100
cout << a.i << " " << a.j;
int n = 300;
// 强行让pa指向n
A* pa = reinterpret_cast<A*>(&n);
// n变成400
pa->i = 400;
// 语句不安全,可能导致程序崩溃
pa->j = 500;
long long la = 0x12345678abcdLL;
// la太长,只取低32位0x5678abcd拷贝给pa
pa = reinterpret_cast<A*>(la);
// pa逐个比特拷贝到u
unsigned int u = reinterpret_cast<unsigned int>(pa);
// 5678abcd
cout << hex << u;
typedef void(*PF1)(int);
typedef int(*PF2)(int, char*);
PF1 pf1; PF2 pf2;
// 两个不同类型的函数指针之间的转换
pf2 = reinterpret_cast<PF2>(pf1);
return 0;
}
用来去除const
属性,将常引用转换成同类型的非常引用,将常指针转换成同类型的非常指针。例如:
const string s = "Inception";
// 将s转换成非常引用
string& p = const_cast<string&>(s);
// 将s转换成非常指针
string* ps = const_cast<string*>(&s);
专门用于将多态基类(包含虚函数的基类)的指针或引用,强制转换为派生类的指针或引用,而且能够检查类型转换的安全性。对于不安全的转换,转换结果会返回NULL
。dynamic_cast
不能用于将非多态基类的指针或引用,强制转换为派生类的指针或引用。示例程序:
#include
#include
using namespace std;
class Base {
public:
// 虚析构函数
virtual ~Base() {}
};
// Derived由Base派生而来
class Derived:public Base {};
int main() {
Base b;
Derived d;
Derived* pd;
// 将基类指针b转化成派生类指针pd
pd = reinterpret_cast<Derived*>(&b);
// reinterpret_cast不检查安全性,不执行下列语句
if (pd == NULL) {
cout << "unsafe1";
}
// 将基类指针b转化成派生类指针pd,不安全
pd = dynamic_cast<Derived*>(&b);
// unsafe2
if (pd == NULL) {
cout << "unsafe2";
}
// 将基类指针pb(指向派生类对象)转化成派生类指针pd,安全
Base* pb = &d;
pd = dynamic_cast<Derived*>(pb);
if (pd == NULL) {
cout << "unsafe2";
}
return 0;
}
程序运行过程中难免会发生各种各样的错误,如数组元素的下标越界、空指针、除数为零等,而造成这些错误的原因主要有代码质量不高存在 B U G {\rm BUG} BUG、输入数据不符合程序要求等。而我们总是希望在发生异常情况时,不只是简单地终止程序的执行,而是能够反馈异常情况的信息,并且能够对程序运行中已发生的事故进行处理。
在 C {\rm C} C++中,解决异常的通用做法有以下几种:在预计会发生异常的地方,加入相应的提示代码,但这种代码通常不是适用的;把异常与函数接口分开,并且能够区分不同的异常,并且在函数体外捕获所发生的异常,并提供更多的异常信息。对于后者, C {\rm C} C++提供了try、catch
语句用于处理异常。
示例程序:
#include
using namespace std;
int main() {
double m, n;
cin >> m >> n;
// 将可能出现异常的语句放入try内
try {
cout << "before dividing." << endl;
// 除零错误,抛出异常
if (n == 0) {
throw - 1;
}
else
{
cout << m / n << endl;
}
cout << "after dividing." << endl;
}
// 没有抛出异常时不会执行catch块语句,否则根据try块中的异常值进入catch分支
// 如上述抛出异常信息为-1,则进入catch (int e){ };块中
catch (double d) {
cout << "catch(double)" << d << endl;
}
catch (int e) {
cout << "catch(int)" << e << endl;
}
cout << "finished." << endl;
return 0;
}
下列语句可以捕获任何形式的异常:
catch (...) {
cout << catch(...) << endl;
}
如果一个函数在执行过程中,抛出的异常在本函数内就被捕获并处理了,那么该异常就不会抛给这个函数的调用者(山一层函数);如果异常在本函数内没有被处理,则该异常就会继续被抛给上一层函数。示例程序:
#include
#include
using namespace std;
class CException {
public:
string msg;
CException(string s) :msg(s) {} // 初始化列表
};
double Devide(double x, double y) {
// 抛出异常为CException类对象
if (y == 0) {
throw CException("divided by zero");
}
cout << "in Devide" << endl;
return 0;
}
int CountTax(int salary) {
try {
// 如果salary小于零则抛出整型异常
if (salary < 0) {
throw - 1;
}
cout << "counting tax" << endl;
}
catch (int) {
cout << "salary 0" << endl;
}
cout << "tex counted." << endl;
return salary * 0.15;
}
int main() {
double f = 1.2;
try {
CountTax(-1); // 参数小于零会引发异常,输出salary 0
f = Devide(3, 0); // 除数为零会引发异常
cout << "end of try block." << endl;
}
// CException类型异常,输出divided by zero
catch (CException e) {
cout << e.msg << endl;
}
cout << "f=" << f << endl;
cout << "finished." << endl;
return 0;
}
标准模板库中有关异常的类都继承自基类exception
,有bad_typeid
、bad_cast
、bad_alloc
、ios_bas::failure
和logic_error
等。
在使用dynamic_cast
进行从多态基类对象或引用到派生类的引用的强制类型转换,如果转换是不安全的,则会抛出此异常。示例程序:
#include
#include
#include
using namespace std;
// 有虚成员函数,是多态基类
class Base {
virtual void func() {}
};
// Derived继承自Base
class Derived :public Base {
public:
void Print() {}
};
void PrintObj(Base& b) {
try {
// 强制类型转换,将b转换成Derived&,即基类引用->派生类引用
Derived& rd = dynamic_cast<Derived&>(b);
// 若转换不安全,会抛出bad_cast异常
rd.Print();
}
// 如果b引用的是基类对象,则转换是不安全的,输出Bad dynamic_cast
catch (bad_cast& e) {
cerr << e.what() << endl;
}
}
int main() {
Base b;
PrintObj(b);
return 0;
}
在使用new
关键字进行动态内存分配时,如果没有足够的内存供使用,则会引发此异常。示例程序:
#include
#include
using namespace std;
int main() {
try {
// 无法分配这么多空间,抛出异常
char* p = new char[0x7fffffff];
}
// 输出bad allocation
catch (bad_alloc& e) {
cerr << e.what() << endl;
}
return 0;
}
用vector
、string
的at
成员函数根据下标访问元素时,如果下标越界,则会抛出此异常。示例程序:
#include
#include
#include
#include
using namespace std;
int main() {
vector<int> v(10);
try {
// 抛出out_of_range异常
v.at(100) = 100;
}
// 捕获异常,输出invalid vector subscript
catch (out_of_range& e) {
cerr << e.what() << endl;
}
string s = "hello";
try {
// 抛出out_of_range异常
char c = s.at(100);
}
// 捕获异常,输出invalid string position
catch (out_of_range& e) {
cerr << e.what() << endl;
}
return 0;
}
在 C {\rm C} C++中,我们可以简单地使用int(...)
等形式进行强制类型转换,但这种方式不检查转换的安全性,本文介绍的几种强制类型转换方式适用于不同的场景。在不同场景使用不同的强制类型转换方式,可以有效地提高程序的可读性。对于异常处理,主要依靠try、catch
块处理,在编写大型程序时,对于利用好异常处理非常重要。因此健壮的程序总是能够自行发现错误,并给出修改意见,而异常处理模块在其中发挥着举足轻重的重要。