c++ 类型转换
Implicit conversion 隐式转换
当一个数据复制为兼容格式的类型时,隐式转换可以自动完成。
请看下面示例:
double a = 100.1;
int b;
b = a;
cout << b << endl;
//output:
//100
以上示例中,我们将 double 类型的数据复制给 int 类型的变量,不会引起语法报错,这就是 Implicit 隐式类型转换,也叫 standard conversion 标准转换。标准转换对一些基本的数据类型有效,能够对一些 numerical 数值类型的数据间进行转换,如:double to int,int to float,short to double,int to bool 等,也可以在 pointer 指针类型间转换。
从小一些的整型如 short 转换到 int 类型,或者从 float 类型转换到 double 类型,这种转换过程叫做 promotion 晋升操作。这种转换可以确保原始数据完整的复制到目标数据类型中。
其他数学运算类型间的转换可能不一定会完整的保留原始数据,下面是几种情况举例:
- 一个负整数转换为 unsigned 类型,结果为 unsigned 类型数据所能表达的最大值数据
- 转换为 bool 类型的数据时,对于数值类型原始数据为 0 时,对于指针类型指针数据为 null pointer 时,对应转换为 false。原始数据为所有其他情况时,转换结果为 true。
- 浮点型数据转换为整型数据,数据会被截取整数部分,如果数据结果超出目标数据类型所能表达的最大值,会得到 undefined
我们可以看到,隐式转换可能会带来数据精度的丢失,编译器此时会提示一条 warning 警告。可以通过使用 explicit conversion 显式转换来避免警告信息。
class 的隐式转换
class 中的隐式转换通过以下三个 function 控制:
- 单参数的 constructors 允许从一个特定类型的数据隐式转换构造为一个 object
- 通过等号操作符 Assignment operator 复用来隐式转换
- 类型传播符 Type-cast 来隐式转换为特定的类型
下面示例解释各种方式的含义:
class A {};
class B {
public:
B(A a) {} //constructor: conversion from A
B operator=(A a) {return *this;} // assignment operator: conversion from A
operator A() {return A();} // type-cast: conversion to A
};
int main()
{
A a; // instance a
B b = a; // constructor b from a
B c(a); // constructor c from a
b = a; // assignment a to b
a = b; // convert b to A type and assignment to a
return 0;
}
以上实例分别介绍了三种隐式转换的方式。
- 构造器的一个传入参数为 A 类型数据,也就是等同于可以将 A 类型因素转换为 B 类型
- 通过操作符复用将等号
=
重新定义,等号右边的为 A 类型数据时,以A类型数据作为构造器参数返回 B 类型 object 指针 - 当执行 B 类型数据赋值给 A 类型时,会通过类型传播符定义的 function 返回 A 类型 object
构造器初始化 object时,当只有一个参数时,就相当于把传入数据转换为对应 object 类型了。上面示例中可以看到,有两种方法来构造 object:
B b = a; // constructor b from a
B c(a); // constructor c from a
以上两种方式都是将 a 作为初始化参数构造 B 类型的 object。
编写了等号操作符复用后,对某个指定类型的外部 object 进行等号操作时就会将其转换为当前 object 类型,而不会报错。
通过类型传播符可以将 object 转换为其他指定类型的 object。
关于操作符复用的语法参考我之前的教程:https://blog.niekun.net/archives/1920.html
explicit 关键词
当调用一个 function 时,对于其传递参数 c++ 允许进行隐式转换,这在一些情况下会引起一些问题,因为我们并不是所有情况下都希望自动进行转换的。
在以上示例中加入 function:
void function(B b) {}
此 function 有一个 B 类型的参数,但是在实际调用中,由于 B 中定义了 A 的隐式转换相关模块,所以我们在这里可以将 A 类型数据作为传入数据:
fun(a);
实际中我们可能并不需要这种转换,我们希望的是这里只能将 B 类型数据作为传入参数。通过关键词 explicit 来定义 constructor 可以实现这个需求,修改 B 的 constructor:
explicit B(A a) {}
再次执行程序,会发现以下几个指令会报错:
B b = a;
fun(a);
通过 关键词 explicit 定义 constructor 的 class 不能通过 assignment 赋值符来初始化 object,也不能对 function 的传入参数进行隐式转换。
explicit conversion 显式转换
c++ 是一种严格区分数据类型的语言,对于那些会影响数据本身的转换,需要进行 explicit conversion 显式转换也叫做 type-casting。
在过去的语法中有两种常规的 type-casting 方式:
double e = 10.11;
int f = int(e);
int g = (int)e;
第一种叫做 function 样式,第二种叫做 c-like C语言模式。
对于那些基础数据类型的数据,这种转换模式没有什么问题,但这种语法对于 class 和 pointer 类型数据也会不加判断的进行转换,从而导致运行时的 runtime error。
为了控制这些在 class 间进行转换的过程,新版 c++ 提供了 4 种 casting operators 传播符来供不同场景下使用:
- dynamic_cast <new_type> (expression)
- reinterpret_cast <new_type> (expression)
- static_cast <new_type> (expression)
- const_cast <new_type> (expression)
dynamic_cast
dynamic_cast 只能用于某个 class 或(void*) 的指针。他的作用是确保转换后的目标类型指针指向的是一个完整有效的 object,而不是空 object。例如,从 derived class 指针转换为 base class 指针。但是对于多态化的 polymorphic class(包含 virtual 元素的 class),当且仅当指向的 object 是一个完整有效的目标 object 类型,使用 dynamic_cast 就可以从 base class 指针转换为 derived class 指针,请看如下示例:
class Base { virtual void dummy() {} };
class Derived: public Base {int a;};
int main()
{
try {
Base *b1 = new Base;
Base *b2 = new Derived;
Derived *d;
d = dynamic_cast<Derived*>(b1);
if (d == 0)
cout << "null pointer on first type cast" << endl;
d = dynamic_cast<Derived*>(b2);
if (d == 0)
cout << "null pointer on second type cast" << endl;
} catch(exception e) {
cout << e.what() << endl;
}
return 0;
}
//output:
//null pointer on first type cast
以上示例中,我们建立了 Base class 和 Derived class,其中 Base 含有一个 virtual function。然后我们创建两个 Base 类型指针,两个指针分别预分配类型为 Base 和 Derived。在之前的 c++ 教程中提到过可以新建 base 类型的变量然后使用 derived 类型数据,需要了解的可以查看:https://blog.niekun.net/archives/1927.html。然后我们创建一个 Derived 类型指针,使用 dynamic_cast 分别将上面建立的两个 Base 类型指针转换为 Derived 类型。
由于 b2 虽然是 Base 类型指针,但是我们预分配内存类型为 Derived 类型,所以它其实包含了 Derived object 所有属性。这样转换后的类型为完整的 Derived 类型指针。所以 d 指针不为空。而 b1 完全是 Base 类型指针,所以转换后的类型是不完整的 Derived 类型指针,所以赋值后结果为空。
dynamic_cast 可以将任意指针转换为 void* 类型指针。
static_cast
static_cast 可以转换任意相关联 class 类型的指针。不仅仅从 derived 到 base,也可以从 base 到 derived。不会判断是否转换到目标指针是完整的数据类型,所以完全由编程人员判断转换操作是否是安全的。相比于 dynamic_cast 有更大适用范围。
以下示例语法不会报错:
class Base {};
class Derived: public Base {};
Base * a = new Base;
Derived * b = static_cast<Derived*>(a);
b 指针会得到一个不完整的 Derived 类型数据,运行时可能报错。因此,使用 static_cast 不仅可以转换那些直接支持隐式转换的 class 指针,也可以在那些不支持转换的 class 间进行转换。
我们测试在其他数据将进行转换:
double a = 12.23;
int b = static_cast<int>(a);
cout << b << endl;
//OUTPUT:
//12
以上,我们将 double 类型的数据转换为 int 类型,这种转换可以直接通过隐式转换完成,但是使用显式转换语法实现更加明确清晰。但前提是原类型和目标类型必须是 related 有关联的 object 类型。
reinterpret_cast
reinterpret_cast 可以将任意类型指针转换为其他任意类型指针,甚至是完全没有关联的两个类型。转换的过程就是将源数据的二进制数据复制到新指针地址。相比于 static_cast 有更大适用范围。
以下代码可以正常执行:
class A { /* ... */ };
class B { /* ... */ };
A * a = new A;
B * b = reinterpret_cast<B*>(a);
此时 b 指针指向的是一个和 B 类型完全不相干的数据,此时访问 b 指向的数据是不安全的。
const_cast
const_cast 可以操作 const 类型的指针数据,例如当一个 function 需要非 const 类型传入数据时,可以通过 const_cast 进行转换。
请看下面示例:
void test(int a) {
cout << a << endl;
}
const int a = 10;
test(a);
以上示例中,调用 test function 会报错,因为传入参数需要是非 const 类型的数据。
修改以上代码:
const int a = 10;
int *b = const_cast<int*>(&a);
test(*b);
//output:
//10
通过 const_cast 将 const int 转换为 int,这样就可以在 function 中使用了。
参考链接
标签:无