C语言的类型转换
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。 1.
隐式类型转化
:编译器在编译阶段自动进行,能转就转,不能转就编译失败 2.显式类型转化
:需要用户自己处理
类型相近的才能发生隐式类型转换,如int和double,如果不相关,而对于指针和整型,指针是地址,整型和指针类型之间不会进行隐式类型转换,只能显式的强制类型转换:
代码语言:javascript复制int main()
{
int i = 1;
//隐式类型转换
double d = i;
printf("%d,%.2f", i, d);
int* p = &i;
//显式强制类型转换
int address = (int)p;
printf("%x,%dn", p, address);
return 0;
}
C 需要四种类型转换
C风格的转换格式很简单,但是有不少缺点的:
1. 隐式类型转化有些情况下可能会出问题:比如数据精度丢失 2. 显式类型转换将所有情况混合在一起,代码不够清晰
因此C 提出了自己的类型转化风格,注意因为C 要兼容C语言,所以C 中还可以使用C语言的转化风格。
C 引入四种类型装换操作符:static_cast、reinterpret_cast、const_cast、dynamic_cast
C 强制类型转换
static_cast
static_cast用于相近类型之间的转换
,(这些类型的表示意义差不多)编译器隐式执行任何类型转换都可以使用static_cast,对于两个不相关类型之间的转换,不能使用static_cast:
int main()
{
int i = 1;
//隐式类型转换
double d = static_cast<double>(i);
printf("%d,%.2f", i, d);
int* p = &i;
//int address = static_cast<int>(p);//错误写法
//printf("%x,%dn", p, address);
return 0;
}
reinterpret_cast
reinterpret_cast用于不相关的类型之间的转换:
代码语言:javascript复制int main()
{
int i = 10;
int* p = &i;
int address = reinterpret_cast<int>(p);
printf("%x,%dn", p, address);
return 0;
}
const_cast
const_cast用于删除变量的const属性,转化后就可以对const变量进行修改了:比如下面的a不可以被修改,现在通过const_cast转化成int*
代码语言:javascript复制int main()
{
const int a = 2;
int* p = const_cast<int*>(&a);
*p = 4;
cout << a << endl;//2
cout << *p << endl;//4
return 0;
}
代码中使用const_cast删除变量a地址的const属性,这时候就可以通过使用指针来修改a的值了。
但是编译器会认为const修饰的变量不会被修改,所以将const修饰的变量存放在寄存器中,当需要读取const变量时会直接从寄存器中读取,而我们修改的实际上是内存中a的值,所以最终打印出来a的值是没有修改之前的。
如果不想让编译器将const变量优化到寄存器中,使用volatile关键字
对const变量进行修饰即可。这时候读取const变量编译器就从内存中进行读取,保持内存的可见性
dynamic_cast
dynamic_cast是用于将父类的指针(引用)转换成子类的指针(引用)
向上转型:子类对象指针/引用——》父类指针/引用(不需要转换,赋值兼容规则)
向下转型:父类对象指针/引用——》子类指针/引用(用dynamic_cast转型是安全的)
注意:
1.dynamic_cast只能用于父类含有虚函数的类 2.dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
向上转型就是我们多态说的切割/切片,是语法天然支持,不需要进行转换,而向下转型语法是不支持的,需要进行强制类型转换
向下转型安全问题:
如果父类的指针(或引用)指向的是一个父类对象,那么将其转换为子类的指针(或引用)是不安全,会存在越界的风险,因为转换后可能会访问子类的资源,而这些资源是父类对象没有的。 如果父类的指针(或引用)指向的是一个子类对象,那么将其转换为子类的指针(或引用)则是安全的,没有问题
使用C强制类型转换向下转型是不安全的,因为此时无论父类的指针(或引用)指向的是父类对象还是子类对象都会进行转换。
使用dynamic_cast向下转型是安全的,如果父类的指针(或引用)指向的是子类对象那么dynamic_cast会转换成功,但如果父类的指针(或引用)指向的是父类对象那么dynamic_cast会转换失败并返回一个空指针。比如:
代码语言:javascript复制class A
{
public:
virtual void f(){}
int _a = 0;
};
class B :public A
{
public:
int _b = 0;
};
void Func(A* ptr)
{
//直接转换是不安全的
// B* bptre = (B*)ptr
B* bptr = dynamic_cast<B*>(ptr);
cout << bptr << endl;
if (bptr)
{
bptr->_a ;
bptr->_b ;
cout << bptr->_a << endl;
cout << bptr->_b << endl;
}
}
int main()
{
A aa;
B bb;
Func(&aa);
Func(&bb);
return 0;
}
如果ptr指向父类,则转换失败,返回空,如果ptr指向子类,则转换成功
如果传入Func函数的是子类对象的地址,那么转化后的bptre与bptr都会有地址,如果传入Func函数的是父类对象的地址,那么转换后的ptre也有地址,而bptr是一个空指针。
explicit
explicit用于修饰构造函数,用于禁止单参数构造函数的隐式转换:
代码语言:javascript复制class A
{
public:
explicit A(int a)
{
cout << "explicit A(int a)" << endl;
}
A(const A& A)
{
cout << "A(const A& a)" << endl;
}
private:
int _a;
};
int main()
{
A a(10);
//A a2 = 11;错误的写法
return 0;
}
A a2 = 11等价于先构造A tmp(11);在拷贝A a2(tmp);
在早期编译器中,遇到A a2=11会先构造临时对象,在用临时对象拷贝构造a2;但是现在的编译器做了优化,遇到A a2=11会直接按照A a2(11)进行处理,这是隐式转换。对于单参自定义类型,A a2=11这种方式可读性不好,所以explicit修饰单参构造函数,进制单参构造函数的隐式转换。
RTTI
RTTI:Run-time Type identification的简称,即:运行时类型识别。
typeid:在运行时识别出一个对象的类型。 decltype:在运行时推演出一个表达式或函数返回值的类型。 dynamic_cast:在运行时识别出一个父类的指针(或引用)指向的是父类对象还是子类对象。
总结
1、C 中的4种类型转换分别是:static_cast,reinterpret_cast、const_cast、dynamic_cast
2、4种类型转换的应用场景:
static_cast
:用于相近类型的类型之间进行转化,如int与double,编译器隐式执行的任何类型都可用static_castreinterpret_cast
:用于两个不相关类型之间的转换const_cast
:用于删除变量的const属性,可用进行修改dynamic_cast
:用于安全地将父类的指针(引用)转换成子类的指针(引用)