在C++中,类型转换可分为"隐式类型转换" 和 "强制类型转换"。
隐式类型转换
隐式转换会发生在以下几种情况中:
- 将一种算术类型值赋给另一种算术类型变量;
- 同一表达式包含不同的数据类型;
- 传递参数给函数时;
- 函数返回表达式的类型与函数返回值类型不一致时。
-
初始化和赋值时进行的转换
在编译器实现以下代码:
char a = 'a';
int b = a;
cout << a << endl;
cout << b << endl;
运行结果:
通过测试可以看出char类型的变量转换为int类型,而且我们并没有做特殊处理,结果完全由编译器自动完成。
在此类型转换中是由char 转换为 int,是由取值范围小的转为取值范围大的,那反过来呢?
执行下列代码:
long long l = 123456789;
float f = l;
cout << l << endl;
cout << f << endl;
运行结果:
由于float只有6位有效数字,所以它截取long long型值的前6位。因此当大范围转小范围会出现精度的丢失。
摘自《C++Primer Plus》潜在的类型数值转换问题:
转换 | 潜在问题 |
将较大浮点类型转换为较小的浮点类型 | 精度(有效数位)降低,值可能超出目标类型的取值范围,在这种情况下,结果将不确定 |
将浮点类型转换为整型 | 小数部分丢失,原来的值可能超出目标类型的取值范围,这种情况下,结果将不确定 |
将较大的整型转换为较小的整型 | 原来的值可能超出目标类型的取值范围,通常只复制右边的字节 |
在C++11中给出了大括号初始化的方式,其对于类型转换要求较为严格。
如执行下述代码:
float f = 1.234567;
int a{ f };
cout << f << endl;
cout << a << endl;
return 0;
结果编译出错
列表初始化不允许收缩转换。
-
表达式中的转换
在表达式中的转换有两种情况:
1. 自某一类型变量出现就自动转换;
2. 混合类型的表达式,较小数据类型会转换为较大数据类型。
上述自动转换发生情况:在C++中枚举类型,布尔型或有符号或无符号的字符、短整数、整数位域。如果一个int可以表示上述类型,则其将被自动转换为整型。这种转换也称为整型提升。
另外一种情况比较好理解:如果表达式有一个int型变量,一个double型变量,则在表达式计算时会将int型提升为double型。
-
参数传递时转换
执行下面代码:
int SquartRoot(double x)
{
// 函数参数被提升至double
return sqrt(x);
}
double Quadratic(int x){
return x*x;
// 返回值被提升至double类型
}
int main()
{
int a = 100;
int res_sqrt = SquartRoot(a);
double res = Quadratic(a);
return 0;
}
小结:
一种算数类型转换为另一种参数类型 | 目标类型是被赋值对象的类型 |
表达式转换 | 整型提升 |
在混合类型的算术表达式中, 最宽的数据类型成为目标转换类型。 | |
将一个表达式作为实参传递给函数调用,形参与实参类型不一致 | 目标转换类型为形参的类型 |
从一个函数返回一个表达式,表达式类型与返回类型不一致 | 目标转换类型为函数的返回类型 |
强制类型转换
首先我们比较熟悉的,C语言所提供的强制类型转换方式:
(TypeName) value; // TypeName是我们所期望的变量类型
C++也提供类似的转换方式:
TypeName (value); // 其意义与C风格一样,这么做只是为了让强制类型转换像是函数调用一样
除此之外就是C++专门提供的四个强制类型转换运算符: (重点)
- static_cast
- dynamic_cast
- const_cast
- reinterpret_cast
作用:可以根据目的选择合适的运算符,而不是通用的类型转换。
-
static_cast
语法: static_cast <type_name> ( Expression )
说明:
- 仅当type_name可被隐式转换为Expression所属类型,或是Expression 可被隐式转换为type_name所属类型,其转换合法;
- 该运算符把expression转换为type_name类型,但没有运行时类型检查来保证转换的安全性。
用法:
- 用于基本数据类型间转换,由于无需进行数据转换,所以enum可以转换为int,double转换为int等等。(安全性需要开发人员来保证)
- 空指针转换为目标类型的空指针。(不安全)
- 把任何类型的表达式转换为void型。
- 用于类层次结构中基类与派生类之间的转换:
上行转换是安全的;
下行转换时,由于没有动态类型检查,所以不是安全的。
注意: static_cast不能转换掉Expression的const、volitale属性。
-
dynamic_cast
语法: dynamic_cast <type_name> ( Expression )
说明:
- 该运算符把Expression 转换成type_name类型的对象。type_name必须是类的指针、类的引用或者void *;
- type_name是类指针,那么Expression 也必须是一个指针。type_name是一个引用,那么Expression 也必须是一个引用。
用法:
- 主要用于类层次间基类与派生类的转换,不同于static_cast的是其下行转换具有类型检测,比较安全。
- 用于类之间的交叉转换。
- 下行转换:
class Base{
/*
要dynamic_cast进行转换一定要有虚函数,
否则编译出错,而static_cast没有此限制
*/
virtual void func()
{}
public:
int _data;
};
class Derived : public Base{
public:
int _val;
};
int main()
{
Base *pb = new Base;
pb->_data = 10;
Derived *pd1 = static_cast<Derived*>(pb);
Derived *pd2 = dynamic_cast<Derived*>(pb);
delete pb;
return 0;
}
通过运行:
可以看出:pd1与pb指向一致,那么对pd1进行操作时,会对b对象产生影响,且访问派生类成员也会出错,所以是不安全的。而pd2成了空指针,就没有之前的安全问题。
- 为什么一定要有虚函数?
因为类型检查所需的运行时类型信息保存在虚函数表中,所以要有虚函数。
- 交叉转换
执行如下代码:
class Base{
virtual void func()
{}
public:
int _data;
};
class Derived_A : public Base{
};
class Derived_B : public Base{
};
int main()
{
Derived_A *pb = new Derived_A;
pb->_data = 10;
Derived_B *pd1 = static_cast<Derived_B*>(pb);
Derived_B *pd2 = dynamic_cast<Derived_B*>(pb);
delete pb;
return 0;
}
执行结果是:static_cast不允许交叉转换,dynamic_cast可以转换且结果为空指针。
-
const_cast
语法: const_cast <type_name> ( Expression )
作用:
当一个值大多数时候是常量,而有时需要进行修改时,可以通过此方式将常量属性修改。
说明:
- type_name与Expression 除了const和volatile特征可以不同以外,其他类型必须相同;
- 常量指针被转换为非常量指针,并且仍然指向原来对象;
- 常量引用被转换为非常量引用,并且仍然指向原来的对象;
- 常量对象转换为非常量对象。
-
reinterpret_cast
语法: reinterpret_cast <type_name> ( Expression )
说明:
- type_name必须时一个指针、引用、算术类型、函数指针、成员指针;
- 它可以把指针转化为整数,也可以把一个整数转换为指针;
- 它并不支持所有类型转换,如:不能将指针转换为更小值的整型或浮点型;不能将函数指针转换为数据指针;
- 在把某一整数转换为原来类型的指针,还可以的到原先的指针值。