C++四种类型转换的引入
1.在C语言中也有类型转换
①例1:相近类型之间可进行隐式类型转换
int a = 10;
double b = a; //将int转换为double,然后赋值给b
②例2:不相近类型可以进行强制类型转换
int a = 10;
int *p = (int*)a; //将int强转为int*
对于强制类型转换来说,有时候意义就会发生改变。
2.由于C语言中类型转换的不太规范,有时候不能区分开哪些类型之间具有隐式类型的转换,哪些具有强制类型转换,所以C++里面就引入了四种类型转换,static_cast、reinterpret_cast、const_cast、dynamic_cast。根据这些转换的名字就可以区分开是何种类型的转换。
static_cast
1.用于相关类型之间进行类型转换(相当于隐式类型转换);
2.用法:
(1)例如可以将int转换为float,使用方式如下:
void test_static_cast()
{
int a = 10;
float b = static_cast<float>(a); //因为b是float类型,所以<>里填float,也就是需要转换的类型
printf("%d %.2f\n", a, b);
}
运行结果:
(2)如果用于不相关类型,例如把一个int转换为int*,则会出错。因为int和int*不是相关类型,不能进行隐式类型的转换,必须要通过强制类型的转换。
reinterpret_cast
1.用于不相关类型进行转换(相当于强制类型转换)。
2.用法:
(1)例1:将一个int类型强制类型转换为int*
void test_static_cast()
{
int a = 10;
int*p = reinterpret_cast<int*>(a);
printf("p = 0x%p\n", p);
}
运行结果:
(2)例2:将一个带有返回值和参数的函数转换为一个不带参数不带返回值的函数指针
#include<iostream>
using namespace std;
typedef void(*FUN)();
int fun(int i)
{
cout << "fun()" << endl;
return 0;
}
int main()
{
FUN f = reinterpret_cast<FUN>(fun);
f();
system("pause");
return 0;
}
运行结果如下:(可以看到将一个带参数和返回值的函数转换为一个不带参数不带返回值的函数指针时,通过那个函数指针可以调用我们的函数,但这是不好的行为)。
const_cast
1.既可以去掉const属性,也可以加上const属性,一般用于去掉const属性(即一般用于将const转换为非const,因为非const可以赋值给const)。
2.由上面可以知道,reinterpret_cast也是用于强制类型转换,为什么还需要const_cast?
因为去掉const属性一般是不安全的,因为去掉之后就可以通过别的变量来修改该const变量的值,如果采用const_cast就能很直观的看到这个转换是去掉const属性的。
3.使用:
int main()
{
const int a = 20;
int *p = const_cast<int*>(&a); //本来不可以将const int*转换为int*,但是加上const_cast之后就可以
cout << *p << endl; //可以得到*p的结果是20
system("pause");
return 0;
}
并且转换后还可以通过非const变量来进行修改:
int main()
{
const int a = 20;
int *p = const_cast<int*>(&a); //p里面存放a的地址,*p = 20
*p = 10; //将p指向的内容进行修改
cout << *p << endl; //则*p = 10,此时在内存窗口里a的值也变成10
cout << a << endl; //但是由于a被const修饰,编译器会优化,则不在内存里面去取a的值,而是在寄存器里面取,就会导致这里输出a的值仍然为20,但实质上a的值已经被修改,为了防止编译器优化,可以在a的前面加上valitale关键字,则每次读取变量的值都去内存中读
system("pause");
return 0;
}
dynamic_cast
1.由于可以直接将父类的对象、指针、引用赋值给子类对象,着是一种切片行为,直接将属于父类的那一部分切给父类,中间没有类型转换,所以也不需要类型转换。
2.父类的对象不能赋值给子类对象,因为子类一般会比父类的成员多,子类多的那一部分父类没有值进行赋值,所以不能将父类的对象赋值给子类的对象。
3.而父类的指针或引用可以赋值给子类的指针或引用,如果直接将指针进行强转这是不安全的,有可能会发现越界访问;因此C++有一个dynamic_cast,可以安全的将父类对象的指针或引用赋值给子类对象的指针或引用。
4.dynamic_cast用于将父类对象的指针或引用转换为子类对象的指针或引用。(并且父类必须包含虚函数,因为是多态的场景)
(1)对于指针而言:
①当父类对象的指针本身指向父类时,不支持将父类的指针或引用赋值给子类对象,那么就不会转换成功,不会转换成功则返回一个空指针(0)。
②当父类对象的指针本身指向子类时,那么将一个指向子类的指针转换成指向子类的指针这是没有问题的,不会越界,这就可以可以转换成功。
(2)对于引用而言
如果转换失败就会抛出一个bad_cast的异常。
5.例如:
例1:
void func(A* pa) //pa是父类的指针
{
B* pb1 = static_cast<B*>(pa);
B* pb2 = dynamic_cast<B*>(pa);
cout << "pb1:" << pb1 << endl;
cout << "pb2:" << pb2 << endl;
}
int main()
{
A a;
B b;
func(&a); //当将a的地址传给func的参数时,此时pa指向父类,将pa转换为B*类型的pb2,会无法转换成功,则pb2就是空指针,pb1不是空指针
func(&b); //将b的地址传给pa时,此时pa指向子类B,则将pa转换为B*的pb2时可以转换成功,并且pb2指向pa指向的子类对象,此时pb1和pb2都不是空指针
system("pause");
return 0;
}
例2:
如果有两个类A和B,B继承了A,A里面包含一个成员变量_a,B里面包含一个成员变量_b,实现一个函数,参数是父类对象的指针,实现一个功能,当该指针指向父类时,输出父类A里的_a,当该指针指向子类B时,输出B里面的_a和_b。
分析:
①如果直接将该父类对象的指针强转为指向子类的指针,那么如果该指针本身指向子类,将它强转为子类,访问子类的成员没有问题;如果该指针本身指向父类,但父类里面不包含_b,就会出现越界访问。
②这时可以考虑用dynamic_cast,可以在运行时识别,当发现该父类对象的指针指向父类,则无法将该指针转换为指向子类的指针,转换失败返回值就为NULL;如果发现该指针本身指向子类对象,那么就可以转换成功,返回值就非空;根据返回值就可以区分开。
class A
{
public:
virtual void f()
{
cout << "A::f()" << endl;
}
int _a;
};
class B :public A
{
public:
virtual void f()
{
cout << "B::f()" << endl;
}
int _b;
};
void func(A* pa) //pa是父类对象的指针
{
B* pb = dynamic_cast<B*>(pa); //将pa转换为指向子类的指针并且将转换后的值赋值给一个指向子类的指针pb
if (pb)
{
//转换成功,说明pa虽然是一个父类对象的指针,但是它指向子类对象,就能赋值给子类对象的指针
cout << "转换成功,说明pa指向子类" << endl;
cout << pb->_a << endl;
cout << pb->_b << endl;
}
else
{
//转换不成功,说明pa就是一个指向父类的指针,此时pb就是空指针
cout << "转换不成功,说明pa指向父类" << endl;
cout << pa->_a << endl;
}
}
int main()
{
A a;
B b;
a._a = 10;
b._a = 100;
b._b = 200;
func(&a); //将a的地址赋值给pa,此时pa就指向父类
func(&b); //将b的地址赋值给pa,此时pa就指向子类
system("pause");
return 0;
}
例3:例如引用的相关场景
void func(A& pa) //pa是父类对象的引用
{
B& pb = dynamic_cast<B&>(pa); //将pa转换为子类对象的引用
//转换成功就会执行下面的语句,说明pa本身引用的就是一个子类对象
//如果转换失败就会抛异常,不会执行下面的语句
cout << "转换成功,说明pa指向子类" << endl;
cout << pb._a << endl;
cout << pb._b << endl;
}
int main()
{
A a;
B b;
a._a = 10;
b._a = 100;
b._b = 200;
//func(a); //此时pa就是父类对象的引用,执行该语句,程序就会崩溃,因为抛异常
func(b); //此时pa就是子类对象的引用,会成功输出_a和_b
system("pause");
return 0;
}