隐式类型转换

标准数据类型之间会进行隐式的类型安全转换

image-20220226105158008

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <string>

using namespace std;

int main()
{
short s = 'a';
unsigned int ui = 1000;
int i = -2000;
double d = i;

cout << "d = " << d << endl;
cout << "ui = " << ui << endl;
cout << "ui + i = " << ui + i << endl;

if( (ui + i) > 0 )
{
cout << "Positive" << endl;
}
else
{
cout << "Negative" << endl;
}

cout << "sizeof(s + 'b') = " << sizeof(s + 'b') << endl;
//编译器为了高效
return 0;
}
1
2
3
4
5
6
7
fengyun@ubuntu:~/share$ g++ test.cpp -o test
fengyun@ubuntu:~/share$ ./test
d = -2000
ui = 1000
ui + i = 4294966296
Positive
sizeof(s + 'b') = 4

注意int 类型会隐式转化为unsigned int类型,17行的ui + i会将i的类型int隐式转换为unsigned int(编译器认为是安全的操作),这是一件非常危险的事情。(这种判断是经常有的,比如vector,string调用length()返回值是size_t即unsigned int,经常与我们自己定义的int类型做运算,那么很容易出现危险的事情)

注意26行sizeof(s + 'b')理应是小字节类型向大字节类型转化,’b’(char)转化为short,而后两个short值相加,最终得到一个short,结果应该是2。这是我的理论分析。
然而编译器会对其进行优化,编译器将s 和’b’都转化为int,对大多数编译器看来,四个字节int类型的运算是最快的,那么编译器会将它们都隐式转化为int类型(编译器认为是安全的操作)。

C语言中强制类型转换

强制类型转换的格式为:

1
2
3
4
5
(type_name) expression

(float) a; //将变量 a 转换为 float 类型
(int)(x+y); //把表达式 x+y 的结果转换为 int 整型
(float) 100; //将数值 100(默认为int类型)转换为 float 类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>

typedef void(PF)(int);

struct Point
{
int x;
int y;
};

int main()
{
int v = 0x12345;
PF* pf = (PF*)v;
char c = char(v);
Point* p = (Point*)v;

pf(5);

printf("p->x = %d\n", p->x);
printf("p->y = %d\n", p->y);

return 0;
}

C方式强制类型转换存在的问题

  • 过于粗暴,任意类型之间都可以进行转换,编译器很难判断其正确性
  • 难于定位在源码中无法快速定位所有使用强制类型转换的语句

新式类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <stdio.h>

void static_cast_demo()
{
int i = 0x12345;
char c = 'c';
int* pi = &i;
char* pc = &c;

c = static_cast<char>(i); //ok
pc = static_cast<char*>(pi); //error
}

void const_cast_demo()
{
const int& j = 1;
int& k = const_cast<int&>(j); //ok

const int x = 2;
int& y = const_cast<int&>(x); //ok

int z = const_cast<int>(x); //error

k = 5;

printf("k = %d\n", k);
printf("j = %d\n", j);

y = 8;

printf("x = %d\n", x);
printf("y = %d\n", y);
printf("&x = %p\n", &x);
printf("&y = %p\n", &y);
}

void reinterpret_cast_demo()
{
int i = 0;
char c = 'c';
int* pi = &i;
char* pc = &c;

pc = reinterpret_cast<char*>(pi); //ok
pi = reinterpret_cast<int*>(pc); //ok
pi = reinterpret_cast<int*>(i); //ok
c = reinterpret_cast<char>(i); //error 不能进行基本类型转换
}

void dynamic_cast_demo()
{
int i = 0;
int* pi = &i;
char* pc = dynamic_cast<char*>(pi); //error
}

int main()
{
static_cast_demo();
const_cast_demo();
reinterpret_cast_demo();
dynamic_cast_demo();

return 0;
}

C + +将强制类型转换分为4种不同的类型

用法:xxx_cast<Type>(Expression)

static_cast强制类型转换

  • 用于基本类型间的转换
  • 不能用于基本类型指针间的转换
  • 用于有继承关系类对象之间的转换和类指针之间的转换

const_cast强制类型转换

  • 用于去除变量的只读属性
  • 强制转换的目标类型必须是指针或引用

reinterpret_cast强制类型转换

  • 用于指针类型间的强制转换
  • 用于整数指针类型间的强制转换

dynamic_cast强制类型转换

  • 用于有继承关系的类指针间的转换
  • 用于有交叉关系的类指针间的转换
  • 具有类型检查的功能
  • 需要虚函数的支持

dynamic_cast 是与继承相关的类型转换关键字
dynamic_cast 要求相关的类中必须有虚函数
用于有直接或者间接继承关系的指针( 引用 ) 之间

指 针 :
转换成功:得到目标类型的指针
转换失败:得到一个空指针

引 用 :
转换成功:得到目标类型的引用
转换失败:得到一个异常操作信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <string>

using namespace std;

class Base
{
public:
Base()
{
cout << "Base::Base()" << endl;
}

virtual ~Base()
{
cout << "Base::~Base()" << endl;
}
};

class Derived : public Base
{

};

int main()
{
Base* p = new Base;

Derived* pd = dynamic_cast<Derived*>(p);//不能用子类的指针指向父类

if( pd != NULL )
{
cout << "pd = " << pd << endl;
}
else
{
cout << "Cast error!" << endl;/
}

delete p;

return 0;
}

普通类型转换为类类型

  • 构造函数可以定义不同类型的参数
  • 参数满足下列条件时称为转换构造函数
    有且仅有一个参数
    参数是基本类型
    参数是其它类类型 (参数不是当前自己类的类型即可)
1
2
3
4
5
6
7
8
9
//旧式C语言的类型转换

int i;
Test t;

i = int(1.5);
t = Test(100);

t = 100;//隐式的t = Test(100);

image-20220226111109298

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <string>

using namespace std;

class Test{
int mValue;
public:
Test(){
mValue = 0;
}
Test(int i){
mValue = i;
}
Test operator + (const Test& p) {
Test ret(mValue + p.mValue);
return ret;
}
int value() {
return mValue;
}
};

int main(){
Test t;

t = 5; // t = Test(5);

Test r;

r = t + 10; // r = t + Test(10);

cout << r.value() << endl;

return 0;
}

观察这段代码28行r = t + 10;这里竟然能够编译通过并且最后的结果是15正确的。但是工程中谁会这样写代码呢?这应该被视作一个错误但是编译器展现了它的能力,这却容易给我们带来一个bug

1
2
3
fengyun@ubuntu:~/share$ g++ test.cpp -o test
fengyun@ubuntu:~/share$ ./test
15
  • 编译器尽力尝试的结果是隐式类型转换
  • 隐式类型转换
    会让程序以意想不到的方式进行工作
    是工程中 bug 的重要来源

explicit

  • 工程中通过 explicit 关键字杜绝编译器的转换尝试
  • 转换构造函数被 explicit 修饰时只能进行显示转换
1
2
3
static_cast<ClassName>(value);
ClassName(value);
(ClassName)value; // 十分不推荐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <string>

using namespace std;

class Test
{
int mValue;
public:
Test()
{
mValue = 0;
}

explicit Test(int i)
{
mValue = i;
}

Test operator + (const Test& p)
{
Test ret(mValue + p.mValue);

return ret;
}

int value()
{
return mValue;
}
};

int main()
{
Test t;

t = static_cast<Test>(5); // t = Test(5);

Test r;

r = t + static_cast<Test>(10); // r = t + Test(10);

cout << r.value() << endl;

return 0;
}

小结

  • 转换构造函数只有一个参数
  • 转换构造函数的参数类型是其它类型
  • 转换构造函数在类型转换时被调用
  • 隐式类型转换是工程中 bug 的重要来源
  • explicit 关键字用于杜绝隐式类型转换

类类型转换到普通类型

  • C+ + 类中可以定义类型转换函数

  • 类型转换函数用于捋类对象转换为其它类型

  • 语 法 规 则

  • ```cpp
    operator Type (){

    Type ret;
    //...
    return ret;
    

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38

    看一段测试代码:

    ```cpp
    #include <iostream>
    #include <string>

    using namespace std;

    class Test
    {
    int mValue;
    public:
    Test(int i = 0)
    {
    mValue = i;
    }
    int value()
    {
    return mValue;
    }
    operator int ()
    {
    return mValue;
    }
    };

    int main()
    {
    Test t(100);
    int i = t; //等价于 int i = t.operator int ();

    cout << "t.value() = " << t.value() << endl;
    cout << "i = " << i << endl;

    return 0;
    }

1
2
3
4
fengyun@ubuntu:~/share$ g++ test.cpp -o test
fengyun@ubuntu:~/share$ ./test
t.value() = 100
i = 100

image-20220226113221572

再看一个类与类之间的转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <string>

using namespace std;

class Test;

class Value
{
public:
Value()
{
}
explicit Value(Test& t)
{
}
};

class Test
{
int mValue;
public:
Test(int i = 0)
{
mValue = i;
}
int value()
{
return mValue;
}
operator Value()
{
Value ret;
cout << "operator Value()" << endl;
return ret;
}
};

int main()
{
Test t(100);
Value v = t;

return 0;
}

Value v = t;可以调用类型转换函数Value v = t.operator Value()也可以调用转换构造函数Value v = Value(t)。这会造成二义性而报错,因此我们在转换构造函数前加上关键字explicit

  • 无法抑制隐式的类型转换函数调用
  • 类型转换函数可能与转换构造函数冲突
  • 工程中以 **Type toType()**的公有成员代替类型转换函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <QDebug>
#include <QString>

int main()
{
QString str = "";
int i = 0;
double d = 0;
short s = 0;

str = "-255";
i = str.toInt();
d = str.toDouble();
s = str.toShort();

qDebug() << "i = " << i << endl;
qDebug() << "d = " << d << endl;
qDebug() << "s = " << s << endl;

return 0;
}

类型识别

在面向对象中可能出现下面的情况
- 基类指针指向子类对象
- 基类引用成为子类对象的別名

image-20220305111513308

  • 静态类型 - 变量( 对象 ) 自身的类型
  • 动态类型 - 指针( 引用 ) 所指向对象的实际类型
1
2
3
4
5
void test(Base* b){
/* 十分危险的转换方式 */
Derived* d = static_cast<Derived*>(b);
//如果动态类型是子类那么转换不会出问题
}

基类指针是否可以强制类型转换为子类指针取决于动态类型

C+ +中如何得到动态类型?

解决方案 - 利用多态

  1. 在基类中定义虚函数返回具体的类型信息
  2. 所有的派生类都必须实现类型相关的虚函数
  3. 每个类中的类型虚函数都需要不同的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>
#include <string>

using namespace std;

class Base
{
public:
virtual string type()
{
return "Base";
}
};

class Derived : public Base
{
public:
string type()
{
return "Derived";
}

void printf()
{
cout << "I'm a Derived." << endl;
}
};

class Child : public Base
{
public:
string type()
{
return "Child";
}
};

void test(Base* b)
{
/* 危险的转换方式 */
// Derived* d = static_cast<Derived*>(b);

if( b->type() == "Derived" )
{
Derived* d = static_cast<Derived*>(b);

d->printf();
}

// cout << dynamic_cast<Derived*>(b) << endl;
}


int main(int argc, char *argv[])
{
Base b;
Derived d;
Child c;

test(&b);
test(&d);
test(&c);

return 0;
}

对类的实际类型判断之后,再使用static_cast转换
dynamic_cast可以判断转换是否成功,转换失败返回0,转换成功返回类的地址。

多态解决方案的缺陷

  • - 必须从基类开始提供类型虚函数
  • - 所有的派生类都必须重写类型虚函数
  • - 每个派生类的类型名必须唯一

C+ +提供了 typeid 关键字用于获取类型信息

  • - typeid 关键字返回对应参数的类型信息
  • - typeid 返回一个 type_info 类对象
  • - 当 typeid 的参数为 NULL 时将拋出异常
1
2
3
4
int i = 0;
const type_info& tiv = typeid(i);
const type_info& tii = typeid(int);
cout << (tiv == tii) << endl;

typeid 的注意事项

  • - 当参数为类型时 : 返回静态类型信息
  • - 当参数为变量时 :
    不存在虚函数表- 返回静态类型信息
    存在虚函数表- 返回动态类型倍息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <string>
#include <typeinfo>

using namespace std;

class Base
{
public:
virtual ~Base(){
}
};

class Derived : public Base
{
public:
};

void test(Base* b)
{
const type_info& tb = typeid(*b);

cout << tb.name() << endl;
}

int main(int argc, char *argv[])
{
int i = 0;

const type_info& tiv = typeid(i);
const type_info& tii = typeid(int);

cout << (tiv == tii) << endl; //输出1

Base b;
Derived d;

test(&b);
test(&d);

return 0;
}
1
2
3
4
5
fengyun@ubuntu:~/share$ g++ test.cpp -o test
fengyun@ubuntu:~/share$ ./test
1
4Base
7Derived

typeid在不同编译器返回信息略有差别