(十五)C++学习 | 强制类型转换 异常处理


1. 强制类型转换

C {\rm C} C++中存在四种形式的强制类型转换:static_castinterpret_castconst_castdynamic_cast

1.1 static_cast

用来进行比较自然低风险的转换,比如整型和实数型、字符型之间相互转换等。static_cast不能在不同类型的指针之间相互转换,也不能用于整型和指针之间的相互转换,也不能用于不同类型的引用之间的相互转换。示例程序:

#include<iostream>

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

1.2 reinterpret_cast

用来进行各种不同类型的指针之间的转换,不同类型的引用之间的转换,指针和能容纳得下指针的整数类型之间的转换。转换的时候,执行的是逐个比特拷贝的操作。示例程序:

#include<iostream>

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

1.3 const_cast

用来去除const属性,将常引用转换成同类型的非常引用,将常指针转换成同类型的非常指针。例如:

const string s = "Inception";
// 将s转换成非常引用
string& p = const_cast<string&>(s);
// 将s转换成非常指针
string* ps = const_cast<string*>(&s);

1.4 dynamic_cast

专门用于将多态基类(包含虚函数的基类)的指针或引用,强制转换为派生类的指针或引用,而且能够检查类型转换的安全性。对于不安全的转换,转换结果会返回NULLdynamic_cast不能用于将非多态基类的指针或引用,强制转换为派生类的指针或引用。示例程序:

#include<string>
#include<iostream>

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

2. 异常处理

程序运行过程中难免会发生各种各样的错误,如数组元素的下标越界、空指针、除数为零等,而造成这些错误的原因主要有代码质量不高存在 B U G {\rm BUG} BUG、输入数据不符合程序要求等。而我们总是希望在发生异常情况时,不只是简单地终止程序的执行,而是能够反馈异常情况的信息,并且能够对程序运行中已发生的事故进行处理。

C {\rm C} C++中,解决异常的通用做法有以下几种:在预计会发生异常的地方,加入相应的提示代码,但这种代码通常不是适用的;把异常与函数接口分开,并且能够区分不同的异常,并且在函数体外捕获所发生的异常,并提供更多的异常信息。对于后者, C {\rm C} C++提供了try、catch语句用于处理异常。

2.1 try、catch处理异常

示例程序:

#include<iostream>

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

2.2 异常再抛出

如果一个函数在执行过程中,抛出的异常在本函数内就被捕获并处理了,那么该异常就不会抛给这个函数的调用者(山一层函数);如果异常在本函数内没有被处理,则该异常就会继续被抛给上一层函数。示例程序:

#include<string>
#include<iostream>

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

3. STL中的异常类

标准模板库中有关异常的类都继承自基类exception,有bad_typeidbad_castbad_allocios_bas::failurelogic_error等。

3.1 bad_cast

在使用dynamic_cast进行从多态基类对象或引用到派生类的引用的强制类型转换,如果转换是不安全的,则会抛出此异常。示例程序:

#include<typeinfo>
#include<iostream>
#include<stdexcept>

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

3.2 bad_alloc

在使用new关键字进行动态内存分配时,如果没有足够的内存供使用,则会引发此异常。示例程序:

#include<iostream>
#include<stdexcept>

using namespace std;

int main() {
	try {
		// 无法分配这么多空间,抛出异常
		char* p = new char[0x7fffffff];
	}
	// 输出bad allocation
	catch (bad_alloc& e) {
		cerr << e.what() << endl;
	}
	return 0;
}

3.3 out_of_range

vectorstringat成员函数根据下标访问元素时,如果下标越界,则会抛出此异常。示例程序:

#include<vector>
#include<string>
#include<iostream>
#include<stdexcept>

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

4. 总结

C {\rm C} C++中,我们可以简单地使用int(...)等形式进行强制类型转换,但这种方式不检查转换的安全性,本文介绍的几种强制类型转换方式适用于不同的场景。在不同场景使用不同的强制类型转换方式,可以有效地提高程序的可读性。对于异常处理,主要依靠try、catch块处理,在编写大型程序时,对于利用好异常处理非常重要。因此健壮的程序总是能够自行发现错误,并给出修改意见,而异常处理模块在其中发挥着举足轻重的重要。


参考

  1. 北京大学公开课:程序设计与算法(三)C++面向对象程序设计.


©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页