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 类型指针,两个指针分别预分配类型为 BaseDerived。在之前的 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_castconst int\ 转换为 int\,这样就可以在 function 中使用了。

参考链接

Type conversions

标签: none

添加新评论