异常,强制类型转换运算符
异常
程序的错误大致可以分为三种,分别是语法错误、逻辑错误和运行时错误:
1) 语法错误在编译和链接阶段就能发现,只有 100% 符合语法规则的代码才能生成可执行程序。语法错误是最容易发现、最容易定位、最容易排除的错误,程序员最不需要担心的就是这种错误。
2) 逻辑错误是说我们编写的代码思路有问题,不能够达到最终的目标,这种错误可以通过调试来解决。
3) 运行时错误是指程序在运行期间发生的错误,例如除数为 0、内存分配失败、数组越界、文件不存在等。C++ 异常(Exception)机制就是为解决运行时错误而引入的。
1.1 什么是异常?
1 处理运行时错误的一种机制
2 将错误的发现和错误的处理分离开,这样便于程序的的维护和开发
1.2 异常的语法
抛出(Throw)--> 检测(Try) --> 捕获(Catch)
注意点:
检测到异常后程序的执行流会发生跳转,从异常点跳转到 catch 所在的位置,位于异常点之后的、并且在当前 try 块内的语句就都不会再执行了;
即使 catch 语句成功地处理了错误,程序的执行流也不会再回退到异常点,所以这些语句永远都没有执行的机会了
#include <iostream>
using namespace std;
int main()
{
try//有可能检测有可能出现异常的代码
{
throw("this is an exception");//抛出异常//现在抛出的字符串,下面用const去接收
}
catch (const char* e)//捕获某一个异常,然后进行异常的处理
{
cout << "exception info " << e << endl;
}
return 0;
}
/*===============================================
* 文件名称:exception_basic_2.cpp
* 创 建 者: memories
* 创建日期:2023年06月05日
* 描 述:have a nice day
================================================*/
#include <iostream>
#include <exception>//异常的头文件
#include <string>
using namespace std;
int main()
{
string s1 = "No man is an island ";
char ch1;
try
{
//try 有可能出现异常的代码
ch1 = s1[100];//非有效的数据,用垃圾数据添满了
cout << "1.ch1=" << ch1 << endl;
}
catch(exception& e)
{
cout << "1.exception info=" << e.what() << endl;
}
try
{
ch1 = s1.at(100);//这个语句产生了异常,如果不去处理这个异常,系统会默认的处理这个异常,退出程序
cout << "2.ch1=" << ch1 << endl;//异常发生后,这里的代码不能被执行
}
catch(exception& e)//标准的异常类一个对象
{
cout << "2.exception info=" << e.what() << endl;
}
return 0;
}
发生异常的位置
1 异常可以发生在当前的 try 块中
2 也可以发生在 try 块所调用的某个函数中
3 try所调用的函数又调用了另外的一个函数,在最后的函数中发生了异常
两重嵌套
/*===============================================
* 文件名称:exception_lay_2.cpp
* 创 建 者: memories
* 创建日期:2023年06月05日
* 描 述:have a nice day
================================================*/
#include <iostream>
#include <exception>
using namespace std;
void func2()
{
cout << "1,in func2()" << endl;
throw("this is exception from func2");
cout << "2,in func2()" << endl;
}
void func1()
{
cout << "1,in func1" << endl;
func2();
cout << "2,in func1" << endl;
}
int main()
{
try
{
func1();
}
catch(const char* e)//抛出关于异常的数据信息,是一个变量,接受异常的信息(指针,数组,字符串,结构体,类)
{
cout << "catch exception:" << e << endl;
}
return 0;
}
异常的数据类型
exceptionType是异常类型,它指明了当前的 catch 可以处理什么类型的异常;
variable是一个变量,用来接收异常信息。当程序抛出异常时,会创建一份数据,这份数据包含了错误信息,程序员可以根据这些信息来判断到底出了什么问题,接下来怎么处理
1 异常类型可以是 int、char、float、bool 等基本类型
2 也可以是指针、数组、字符串、结构体、类等聚合类型
3 C++ 语言本身以及标准库中的函数抛出的异常,都是 exception 类或其子类的异常
catch/多级catch
catch:
我们可以将 catch 看做一个没有返回值的函数,当异常发生后 catch 会被调用,并且会接收实参(异常数据)
但是 catch 和真正的函数调用又有区别:
1 真正的函数调用,形参和实参的类型必须要匹配,或者可以自动转换,否则在编译阶段就报错了。
2 而对于catch,异常是在运行阶段产生的,它可以是任何类型,没法提前预测,所以不能在编译阶段判断类型是否正确,只能等到程序运行后,真的抛出异常了,
再将异常类型和 catch 能处理的类型进行匹配,匹配成功的话就“调用”当前的 catch,否则就忽略当前的 catch
多级catch:
在try的代码里,有可能会出现抛出多种的异常,所以我的异常处理要采用多级的catch,如果不多级捕获,系统会中止程序的执行
try{
//可能抛出异常的语句
}catch (exception_type_1 e){
//处理异常的语句
}catch (exception_type_2 e){
//处理异常的语句
}
//其他的catch
catch (exception_type_n e){
//处理异常的语句
}
catch 在匹配过程中的类型转换
1 向上转型
2 const转换
3 数组或函数指针转换
其他的类型转换都不能应用于catch。
多重检测和接收错误
/*===============================================
* 文件名称:exception_catch.cpp
* 创 建 者: memories
* 创建日期:2023年06月05日
* 描 述:have a nice day
================================================*/
#include <iostream>
#include <exception>
#include <string>
using namespace std;
void func1(int a)
{
if(a==1)
{
throw("exception 1 in func1");
}
else if(a==2)
{
string s = "exception 2 in func1";
throw(s); //抛出string的对象
}
else if(a==3)
{
int aa = 100;
throw(aa);
}
}
int main()
{
try
{
cout << "1_1" << endl;
func1(3);
cout << "1_2" << endl;
}
catch(const char* e)
{
cout << "1,exception info=" << e << endl;
}
catch(const string& e)
{
cout << "2,exception info=" << e << endl;
}
catch(int e)
{
cout << "3,exception info=" << e << endl;
}
return 0;
}
catch 在匹配过程中的类型转换
catch 在匹配过程中的类型转换
1 向上转型
2 const转换
3 数组或函数指针转换
其他的类型转换都不能应用于catch。
throw
1 在 C++ 中,我们使用 throw 关键字来显式地抛出异常,
throw 的语法:
throw exceptionData;
异常必须显式地抛出,才能被检测和捕获到;如果没有显式的抛出,即使有异常也检测不到
2 throw用作异常规范(了解,不需要掌握)
throw 关键字除了可以用在函数体中抛出异常,还可以用在函数头和函数体之间,指明当前函数能够抛出的异常类型,这称为异常规范(Exception specification)
double func(char param) throw(int); //该函数有可能抛出一个整型的异常数据
double func1(string a) throw(char, int, exception); //该函数有可能抛出3钟异常数据
double func2(int a) throw(); //该函数不抛出异常
异常规范是 C++98 新增的一项功能,但是后来的 C++
11 已经将它抛弃了,不再建议使用
请抛弃异常规范,不要再使用它
C++ exception类:C++标准异常的基类
class exception{
public:
exception() throw(); //构造函数
exception(const exception&) throw(); //拷贝构造函数
exception& operator= (const exception&) throw(); //运算符重载
virtual ~exception() throw(); //虚析构函数
virtual const char* what() const throw(); //虚函数
}
在代码中可以直接使用标准异常类
自定义异常
1 从标准异常派生
/*===============================================
* 文件名称:inherit_std_exception.cpp
* 创 建 者: memories
* 创建日期:2023年06月05日
* 描 述:have a nice day
================================================*/
#include <iostream>
#include <exception>
using namespace std;
class newException:public exception
{
public:
newException(const char * msg):errMsg(msg){}
virtual const char * what() const noexcept//如果要向上转型,必须严格的重写覆盖
{
return errMsg;
}
private:
const char * errMsg;
};
int divide(int x,int y)
{
if( 0==y )
{
newException e("the divisor is 0");
throw(e);
}
else
return x/y;
}
int main()
{
try
{
int x = 9;
int y = 0;
int a = divide(x,y);
}
catch (exception & e)
{
cout << "the error info is " << e.what() << endl;
}
return 0;
}
2 创建全新的异常类
/*===============================================
* 文件名称:new_exception.cpp
* 创 建 者: memories
* 创建日期:2023年06月05日
* 描 述:have a nice day
================================================*/
#include <iostream>
#include <exception>
using namespace std;
class newException
{
public:
newException(const char * msg):errMsg(msg){}
virtual const char * what() const noexcept//如果要向上转型,必须严格的重写覆盖
{
return errMsg;
}
private:
const char * errMsg;
};
int divide(int x,int y)
{
if( 0==y )
{
newException e("the divisor is 0 in newException");
throw(e);
}
else
return x/y;
}
int main()
{
try
{
int x = 9;
int y = 0;
int a = divide(x,y);
}
catch (newException & e)
{
cout << "the error info is " << e.what() << endl;
}
return 0;
}
强制类型转换运算符
C++ 引入了四种功能不同的强制类型转换运算符以进行强制类型转换:
编译期间就确定转换:
static_cast
reinterpret_cast
const_cast
运行期间蔡确定转换:
dynamic_cast
基本语法:
C++ 强制类型转换运算符的用法如下:
强制类型转换运算符<要转换到的类型>(待转换的表达式)
例如:
float a = 3.14
int b = static_cast<int>(a); //将单浮点数转整数
static_cast
static_cast<type-id> (expression)
1 用于基本数据类型之间的转换,如把int转换成char,把float转换成int
2 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的
3 把空指针转换成目标类型的空指针
4 把任何类型的表达式转换成void类型
/*===============================================
* 文件名称:static_cast.cpp
* 创 建 者: memories
* 创建日期:2023年06月05日
* 描 述:have a nice day
================================================*/
#include <iostream>
using namespace std;
class A
{
public:
void show()
{
cout << "a=" << a << ",b=" << b << endl;
}
int a;
int b;
};
class B:public A
{
public:
void show()
{
cout << "a=" << a << ",b=" << b << ",c=" << c << endl;
}
int c;
};
int main()
{
//example 1//
int a = 49;
char b = static_cast<char>(a);//静态类型的强转符
cout << "a=" << a << ",b=" << b << endl;
float c = 6.666;
int d = static_cast<int>(c);
cout << "c=" << c << ",d=" << d << endl;
/example 2/
A aa;
aa.a = 100;
aa.b = 200;
A *pA = NULL;
A aa1;
aa1.a = 101;
aa1.b = 202;
B bb;
bb.a = 1000;
bb.b = 2000;
bb.c = 3000;
B *pB = NULL;
//向上转型,safe
pA = static_cast<A*>(&bb);
pA->show();
//向下转型,unsafe
pB = static_cast<B*>(&aa);
pB->show();
return 0;
}
reinterpret_cast
特别灵活,特别危险,它的安全要程序员自己来保证
reinterpret_cast<type-id> (expression)
用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换
1 改变指针或引用的类型
2 将指针或引用转换为一个足够长度的整形
3 将整型转换为指针或引用类型
/*===============================================
* 文件名称:reinterpret_cast.cpp
* 创 建 者: memories
* 创建日期:2023年06月05日
* 描 述:have a nice day
================================================*/
#include <iostream>
using namespace std;
class Demo
{
public:
int a;
int b;
void show()
{
cout << "a=" << a << ",b=" << b << endl;
}
};
int main()
{
///example 1//
long aa = 0x00122112334;
// char *pcha = static_cast<char *>(&aa);
char *pchar = reinterpret_cast<char *>(&aa);
cout << "aa=" << aa << endl;//作为十进制打印出来
cout << "pchar=" << pchar << endl;
//example 2//
Demo d1;
d1.a = 100;
d1.b = 200;
d1.show();
int *pInt = reinterpret_cast<int*>(&d1);
cout << "*pInt = " << *pInt << endl;
/exacmple 3//
char c1 = '9';
long l1 = reinterpret_cast<long>(&c1);//用l1把c1的地址包含进去,使得l1变成一个指针
cout << "l1=" << l1 << endl;
pchar = reinterpret_cast<char *>(l1);
cout << "*pchar=" << *pchar << endl;
return 0;
}
const_cast
const_cast 运算符仅用于进行去除 const 属性的转换
将 const 引用转换为同类型的非 const 引用,将 const 指针转换为同类型的非 const 指针时可以使用 const_cast 运算符
/*===============================================
* 文件名称:const_cast.cpp
* 创 建 者: memories
* 创建日期:2023年06月05日
* 描 述:have a nice day
================================================*/
#include <iostream>
using namespace std;
void func(const int& a)
{
//const_cast的转化只针对引用和指针
int& a_r = const_cast<int&>(a);//去掉引用的const属性,让其可以被修改
a_r++;
}
int main()
{
int aa = 100;
cout << "aa=" << aa << endl;
func(aa);
cout << "aa=" << aa << endl;
return 0;
}
dynamic_cast
dynamic_cast<type-id> (expression)
1 其他三种都是编译时完成的,dynamic_cast 是运行时处理的,运行时要进行类型检查
2 不能用于内置的基本数据类型的强制转换
3 dynamic_cast 要求 <> 内所描述的目标类型必须为指针或引用。dynamic_cast 转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回 nullptr
4 在类的转换时,在类层次间进行上行转换(子类指针指向父类指针)时,dynamic_cast 和 static_cast 的效果是一样的。
在进行下行转换(父类指针转化为子类指针)时,dynamic_cast具有类型检查的功能,比 static_cast 更安全。
向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败。
5 使用dynamic_cast进行转换的,基类中一定要有虚函数,否则编译不通过(类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义)
/*===============================================
* 文件名称:dynamic_cast.cpp
* 创 建 者: memories
* 创建日期:2023年06月05日
* 描 述:have a nice day
================================================*/
#include <iostream>
using namespace std;
class A
{
public:
virtual void show()
{
cout << "a=" << a << ",b=" << b << endl;
}
int a;
int b;
};
class B:public A
{
public:
void show()
{
cout << "a=" << a << ",b=" << b << ",c=" << c << endl;
}
int c;
};
int main()
{
A a1;
a1.a = 100;
a1.b = 200;
A *pA = NULL;
A *pA2 = &a1;
B b1;
b1.a = 1000;
b1.b = 2000;
b1.c = 3000;
B *pB = NULL;
//向上转型
pA = dynamic_cast<A*>(&b1);
// pA = (&b1);
if(pA != nullptr)//必须判断一下转换是否成功
{
pA->show();
}
else
{
cout << "dynamic_cast failed 1" << endl;
}
//向下转型
pB = dynamic_cast<B*>(pA2);
if(pB != nullptr)//必须判断一下转换是否成功
{
pB->show();
}
else
{
cout << "dynamic_cast failed 2" << endl;
}
/
//基类的指针转换为派生的指针能否成功,取决于基类的指针是否真正的指向了派生类的对象
//这个时候,基类指针指向了派生类的对象,所以能够转换成功
//和static_cast是不一样的
pA2 = &b1;
pB = dynamic_cast<B*>(pA2);//转换是看指向的对象,而不是看指针的类型
if(pB != nullptr)//必须判断一下转换是否成功
{
pB->show();
}
else
{
cout << "dynamic_cast failed 2" << endl;
}
return 0;
}
/*===============================================
* 文件名称:dynamic.cpp
* 创 建 者: memories
* 创建日期:2023年06月06日
* 描 述:have a nice day
================================================*/
#include <iostream>
using namespace std;
class A
{
public:
virtual void show()
{
cout << "A::show()" << endl;
}
};
class B:public A
{
public:
void show()
{
cout << "B::show()" << endl;
}
};
class C:public B
{
public:
void show()
{
cout << "C::show()" << endl;
}
};
void print(int id)
{
cout << id << ": dynamic cast failed" << endl;
}
int main()
{
A obja;
B objb;
C objc;
A *pA = NULL;
B *pB = NULL;
C *pC = NULL;
A->A///
A *p = &obja;
pA = dynamic_cast<A*>(p);
if(pA != NULL)
{
pA->show();
}
else
{
print(1);
}
A->B///
pB = dynamic_cast<B*>(p);
if(pB != NULL)
{
pB->show();
}
else
{
print(2);
}
A->C///
pC = dynamic_cast<C*>(p);
if(pC != NULL)
{
pC->show();
}
else
{
print(3);
}
B->A///
B *q = &objb;
pA = dynamic_cast<A*>(q);
if(pA != NULL)
{
pA->show();
}
else
{
print(1);
}
B->B///
pB = dynamic_cast<B*>(q);
if(pB != NULL)
{
pB->show();
}
else
{
print(2);
}
C->C///
pC = dynamic_cast<C*>(q);
if(pC != NULL)
{
pC->show();
}
else
{
print(3);
}
C->A///
C *n = &objc;
pA = dynamic_cast<A*>(n);
if(pA != NULL)
{
pA->show();
}
else
{
print(1);
}
C->B///
pB = dynamic_cast<B*>(n);
if(pB != NULL)
{
pB->show();
}
else
{
print(2);
}
C->C///
pC = dynamic_cast<C*>(n);
if(pC != NULL)
{
pC->show();
}
else
{
print(3);
}
return 0;
}
static_cast和dynamic_cast的区别
static_cast和dynamic_cast的区别
static_cast 它是在编译期间就确定了,向上转换和向下转换都是可以的,但是只有向上转换是安全的
dynamic_cast 它是运行期间来确定是否转换成功,支持向上转换,就是只有我的指针真正指向了我要转换的类型,才允许转换成功,需要在代码里检测转换的结果,转换的指针如果不为空,表示转换成功,否则转换失败