> 文章列表 > C++_07----强制类型转换异常

C++_07----强制类型转换异常

C++_07----强制类型转换异常

目录

  • 强制类型转换
    • 1、static_cast
      • 1.1 基本数据类型之间的转换
      • 1.2 自定义数据类型转换
      • 1.3 注意:static_cast会导致导致运行期错误。
    • 2、dynamic_cast
      • 2.1 内置数据类型转换
      • 2.2 自定义数据类型转换
    • 3、const_cast
    • 4、reinterpret_cast
  • 异常
    • 1、栈解旋(unwinding)
    • 2、异常变量的寿命周期
      • 2.1、抛出: throw MyException(); 捕获:catch (MyException e);
      • 2.2、抛出: throw MyException(); 捕获:catch (MyException &e);
      • 2.3、抛出: throw &MyException(); 捕获:catch (MyException *e);
      • 2.4、抛出: throw new MyException(); 捕获:catch (MyException *e);elete e;
    • 3、异常的多态
    • 4、使用系统标准异常类

强制类型转换

在c语言的基础上,C++新增加了四种强制类型转换:
1、静态转换 static_cast<new_type> (expression)
2、动态转换 dynamic_cast<new_type> (expression)
3、常量转换 const_cast<new_type> (expression)
4、重新解释转换 reinterpret_cast<new_type> (expression)

1、static_cast

静态类型转换 — static_cast:在转换时,在编译期间会对类型进行检查,运行时不会检查类型转换的安全性

语法: static_cast<new_type> (expression)

1.1 基本数据类型之间的转换

1.内置数据类型转换,例如int转换为char,char转换为double等;
2.将空指针转换为目标类型的空指针
3.将任何类型的表达式都转换为void类型
4.将non-const变量转换为const变量注意:static_cast不能转换掉expression的const、volitale或者__unaligned属性
void static_cast_test()
{//1、对于内置数据类型转换char a = 'a';double d = static_cast<double> (a);cout << d << endl;
}

1.2 自定义数据类型转换

static_cast还可以转换对象的指针类型,但它不进行运行时类型检查,如用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换
1.进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
2.进行下行转换(把基类的指针或引用转换为派生类表示),由于没有动态类型检查,所以是不安全的;
3.无继承关系的类不能转换
注意:static_cast不能移掉expression中的const,volatile以及_unaligned属性

class Father{};
class Son :public Father{};
class Other{};
void static_cast_test()
{//对于有继承关系的类的转换Father *base = NULL;//1、将 Father * 转换为 Son * : 父转子,不安全Son *son = static_cast<Son *>(base);//2、将 Son * 转换为Father * : 子转父,安全Father *Base2 = static_cast<Father *>(son);//3、将 Father * 转换为 Other * : 无继承关系,强转无效//Other *oth = static_cast<Other *>(base);//compile error,编译未通过,类型转换无效
}

1.3 注意:static_cast会导致导致运行期错误。

本例中定义了两个类:base类和derived 类,这两个类构成继承关系。在前面介绍多态时,我们一直是用基类指针指向派生类或基类对象,而本例则不同。
本例主函数中定义的是一个派生类指针,当我们将其指向一个基类对象时,这是错误的,会导致编译错误。但是通过强制类型转换我们可以将派生类指针指向一个基类对象,
p = static_cast<derived *>(new base) 语句实现的就是这样一个功能,这样的一种强制类型转换时合乎C++语法规定的,但是是非常不明智的,它会带来一定的危险。**

#include<iostream>
using namespace std;class base
{
public : void m(){cout<<"m"<<endl;}
}; 
class derived : public base
{
public:void f(){cout<<"f"<<endl;}
};
int main()
{derived * p;p = new base;p = static_cast<derived *>(new base);p->m();p->f();return 0;
}

在程序中 p 是一个派生类对象,我们将其强制指向一个基类对象。
① 首先通过p指针调用 m 函数,因为基类中包含有m函数,这一句没有问题;
② 之后通过p指针调用 f 函数。一般来讲,因为p指针是一个派生类类型的指针,而派生类中拥有f函数,因此p->f();这一语句不会有问题,但是本例中p指针指向的却是基类的对象,而基类中并没有声明 f函数 ,虽然p->f();这一语句虽然仍没有语法错误,但是它却产生了一个运行时的错误。换言之,p指针是派生类指针,这表明程序设计人员可以通过p指针调用派生类的成员函数f,但是在实际的程序设计过程中却误将p指针指向了一个基类对象,这就导致了一个运行期错误。

产生这种运行期的错误原因在于**static_cast强制类型转换时并不具有保证类型安全的功能,而C++提供的dynamic_cast却能解决这一问题,dynamic_cast可以在程序运行时检测类型转换是否类型安全。**

2、dynamic_cast

动态类型转换 — dynamic_cast:是将一个基类对象指针或者引用转换到继承类类指针或者引用, dynamic_cast用来检查两者是否有继承关系,因此实际上只接受基于类对象的指针或者引用的转换
只针对安全的转换会成功!!

语法为:
dynamic_cast<new_type*>(expression):new_type必须为有效指针
dynamic_cast<new_type&>(expression):new_type必须为左值
dynamic_cast<new_type&&>(expression):new_type必须为右值

注意
1、如果 new_type是指针类型,那么expression也必须是一个指针;如果 new_type 是一个引用,那么 expression 也必须是一个引用
2、使用dynamic_cast进行转换的,基类中一定要有虚函数,否则编译不通过
3、向下转换,即将父类指针转化子类指针。向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败。

2.1 内置数据类型转换

void dynamic_cast_test()
{//对于内置数据类型转换  ---  失败char a = 'a';double b = dynamic_cast<double>(a);  //ERR,内置数据类型 无法转换
}

2.2 自定义数据类型转换

  1. 在类层间进行 上行转换 时,dynamic_cast 和 static_cast效果一样
  2. 在类层间进行 下行转换 时,具有类型检查的功能,比static_cast更安全
    (1)如果 下行转换是 安全的(即:基类指针或者引用指向一个派生类对象),这个运算符会传回适当转型过的指针
    (2)如果 下行转换是 不安全的(即:基类指针或者引用没有指向一个派生类对象),这个运算符会传回空指针

dynamic_cast可以在执行期决定真正的类型

class Father2 { virtual void func() {} };
class Son2 :public Father2 { virtual void func() { cout << "ddd" << endl; } };
class Other2 {};
void dynamic_cast_test()
{//1、将 Father * 转换为 Son * : 父转子,不安全  ----  动态转换失败Father *father = NULL;//Son *son = dynamic_cast<Son *>(father);    //ERR,将基类转化为派生类,父转子,不安全//2、将 Son * 转换为Father * : 子转父,安全    ----  动态转换成功Son *son = NULL;father = dynamic_cast<Father*>(son);  //OK,派生类转化为基类,子转父,安全//3、将 Father * 转换为 Other * : 无继承关系,强转无效Father *base = NULL;//Other *oth = dynamic_cast<Other *>(base);//ERR,编译未通过,类型转换无效//4、如果发生多态 ,则动态转换总是安全的(发生多态的条件 --- 子类重写父类的虚函数)Father2 *father2 = new Son2;    			 //父类指针指向子类对象,已经在使用多态Son2 *son2 = dynamic_cast<Son2 *>(father2);  //将父类指针转换为子类指针
}

3、const_cast

常量转换 — const_cast:用于修改类型的const或volatile属性,主要用于去掉const属性,该运算符用来修改类型的const或volatile属性。

注意:
在C语言中,const限定符通常被用来限定变量,用于表示该变量的值不能被修改。而const_cast则正是用于强制去掉这种不能被修改的常数特性,但需要特别注意的是const_cast不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用

语法: const_cast < new_type > ( expression )
除了const 或 volatile修饰之外, new_type和expression的类型是一样的。

主要应用场景:

  1. 常量指针 转化成 非常量的指针,并且仍然指向原来的对象;
  2. 常量引用 转换成 非常量的引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

const_cast一般用于修改指针,如const char *p形式

【例一】

const int a = 10;
const int * p = &a;
*p = 20;                  //compile error
int b = const_cast<int>(a);  //compile error

在【例一】中出现了两个编译错误,第一个编译错误是*p因为具有常量性,其值是不能被修改的;另一处错误是const_cast强制转换对象必须为指针或引用,而【例一】中为变量a,这是不允许的!

【例二】const_cast使用

void const_cast_test()
{const int *p = NULL;int *pp = const_cast<int *>(p);   // const指针 转换为 非const指针const int *ppp = const_cast<const int *>(pp); //非const指针 转换为 const指针int a = 10;int &b = a;const int & b_ref = const_cast<const int &>(b); //将 非const引用 转换为 const引用
}

4、reinterpret_cast

重新解释转换 — reinterpret_cast :最不安全的一种转换
在C++语言中,reinterpret_cast主要有三种强制转换用途:(1)改变指针或引用的类型(2)将指针或引用转换为一个足够长度的整形(3)将整型转换为指针或引用类型

语法: reinterpret_cast < new_type > ( expression )
new_type必须是一个指针、引用、算术类型、函数指针或者成员指针。

(1)它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值),
(2)它是这四种转换运算符中最不安全的,
(3)它主要适用于依赖底层实现的编程技术,因为不系统一个类型存储的字节可能是不一样的,所以具有不可移植性、对平台的依赖性强

void reinterpret_cast_test()
{int a = 10;//int *p = reinterpret_cast<int *>(a);//直接将int型转换为指针,整型变量值随意设置,转换为指针随意指向,不安全Father *father = NULL;Other *other = reinterpret_cast<Other*>(father);//将两个没有关联的类指针进行转换}

异常

class MyException
{
public:MyException(){cout << "对象被 构造函数 创建" << endl;}MyException(MyException &p){cout << "拷贝构造函数" << endl;}~MyException(){cout << "对象被 析构函数 析构" << endl;}void PrintError(){cout << "我自己的异常处理" << endl;}
};
int MyDivision(int a, int b)
{if (b == 0){//return -1;//throw 1; //抛出一个int异常//throw 3.14; //抛出一个double异常throw MyException(); //抛出一个MyException的异常对象,是一个匿名对象}return a / b;
}
void Exception_func()
{int A = 10;int B = 0;//在可能出现异常的地方没写到try代码块中try{int ret = MyDivision(A, B);}catch (int){cout << "int类型异常捕获" << endl;}catch (double){//如果捕获到异常但并不处理,想让调用方处理,可以使用 throw 关键字;//throw;cout << "double类型异常捕获" << endl;}catch (MyException a){a.PrintError();}catch (...){cout << "其他类型异常捕获" << endl;}
}
void Exception_test01()
{try{Exception_func();}catch(double){cout << "在Exception_test01()函数中,处理异常" << endl;}catch (...){cout << "在Exception_test01()函数中,其他类型,处理异常" << endl;}//如果程序中没有任何一处地方去处理异常,则系统会主动调用terminate函数,使程序终止
}

1、栈解旋(unwinding)

进入 try{ } 起,一直到异常被 throw (抛出)前,这期间在栈上构造的所有对象,都属被自动析构,析构的顺序与构造的顺序相反,这一过程叫做–栈解旋

class Person
{
public:Person(string name){this->m_Name = name;cout << "对象被 构造函数 创建" << endl;}Person(Person &p){cout << "拷贝构造函数" << endl;}~Person(){cout << "对象被 析构函数 析构" << endl;}string m_Name;
};
void unwinding_test()
{Person p1("lalalal");
}

2、异常变量的寿命周期

2.1、抛出: throw MyException(); 捕获:catch (MyException e);

调用顺序构造==> 拷贝构造 == > 捕获异常==>析构
缺点:调用拷贝构造函数产生新的对象

void Func01()
{throw MyException();
}
void test01()
{try{Func01();}catch (MyException e){cout << "我的异常捕获" << endl;}
}
void Exception_LifeTime_test()
{//抛出: throw MyException();  捕获:catch (MyException e);                test01();
}

2.2、抛出: throw MyException(); 捕获:catch (MyException &e);

调用顺序构造===>捕获异常 ==>析构 (推荐使用)

void Func02()
{throw MyException();
}
void test02()
{try{Func02();}catch (MyException &e){cout << "我的异常捕获" << endl;}
}
void Exception_LifeTime_test()
{test02();
}

2.3、抛出: throw &MyException(); 捕获:catch (MyException *e);

调用顺序构造===>析构===>捕获异常
缺点:提前释放异常的对象

void Func03()
{throw &MyException();
}
void test03()
{try{Func03();}catch (MyException *e){cout << "我的异常捕获" << endl;}}
void Exception_LifeTime_test()
{test03();  
}

2.4、抛出: throw new MyException(); 捕获:catch (MyException *e);elete e;

调用顺序构造===>捕获异常===>析构 (与test02一致,推荐使用)
缺点:和引用效果一样,但是要手动释放

void Func04()
{throw new MyException();
}
void test04()
{try{Func04();}catch (MyException *e){cout << "我的异常捕获" << endl;delete e;}
}
void Exception_LifeTime_test()
{test04();
}

3、异常的多态

class BaseException //异常的基类
{
public:virtual void PrintERR(){}
};
class NULLptr :public BaseException  //空指针异常类
{
public:virtual void PrintERR(){cout << "空指针异常" << endl;}
};
class OutOfRange :public BaseException  //越界异常
{
public:virtual void PrintERR(){cout << "越界异常" << endl;}
};void func()
{//抛出不同类的异常,可以使用异常的多态//throw NULLptr();throw OutOfRange();
}
void Exception_test()
{try{func();}catch (BaseException &e){e.PrintERR();  //不同子类打印异常的接口相同}
}

4、使用系统标准异常类

#include //标准异常头文件;通过what()函数打印具体的错误信息.

class People
{
public:People(int age){if (age < 0 || age>150){throw out_of_range("年龄在0---150之间");}else{this->m_Age = age;}}int m_Age;
};
void Std_Excep_test01()
{try{People p1(1000);}catch (exception &p){cout << p.what() << endl;//通过what()函数打印具体的错误信息}}
void Exception_Standard_test()
{Std_Excep_test01();
}