再论类型转换

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

clipboard.png

实例分析: 有趣的隐式类型转换

#include <iostream>

using namespace std;

int main()
{
    short s = 'a';
    unsigned int ui = 1000;
    int i = -2000;
    double d = i;
    
    cout << "d = " << d << endl;
    cout << "i = " << i << 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;
}
输出:
d = -2000
i = -2000
ui = 1000
ui + i = 4294966296
Positive
sizeof(s + 'b') = 4

分析 1:
cout << "ui + i = " << ui + i << endl;
if( (ui + i) > 0 ) {}

ui 类型为 unsigned int; 
i  类型为 int;
操作数类型不一致,i 将发生隐式类型转换到 unsigned int ,内存中的二进制数将被重新解读。计算结果为 unsigned int 类型。

分析 2:
sizeof(s + 'b');

s 类型为 short 
'b' 类型为 char
操作数类型不一致,s 将发生隐式类型转换到 int, 'b' 将发生隐式类型转换到 int。计算结果为 int 类型。

问: 为什么不是 'b'(char) 隐式转换为 s (short) 类型呢?
答: 4 字节整型数计算最高效

问题

普通类型与类类型之间能否进行类型转换?
类类型之间能否进行类型转换?

编程实验: 普通类型 -> 类类型

#include <iostream>

using namespace std;

class Test
{
};

int main()
{
    Test t;
    
    t = 5;
    
    return 0;    
}
输出:
test.cpp: In function ‘int main()’:
test.cpp:13: error: no match for ‘operator=’ in ‘t = 5’
test.cpp:6: note: candidates are: Test& Test::operator=(const Test&)

结论:
编译器不支持普通类型到类类型的直接转换

转换构造函数

  • 构造函数可以定义不同类型的参数
  • 参数满足下列条件时称为转换构造函数

    • 有且仅有一个参数
    • 参数是基本类型
    • 参数是其它类类型

编程实验: 普通类型 -> 类类型

#include <iostream>

using namespace std;

class Test
{
private:
    int mValue;
public:
    Test()
    {
        cout << "Test()" << endl;
        mValue = 0;
    }
    Test(int i)
    {
        cout << "Test(int i), i = " << i << endl;
    
        mValue = i;
    }
    int value()
    {
        return mValue;
    }
    ~Test()
    {
        cout << "~Test(), mValue = " << mValue << endl;
    }
};

int main()
{
    Test t;
    
    t = 5;                // 注意这里!
    
    cout << "t.value() = " << t.value() << endl;
    
    return 0;    
}
输出:
Test()
Test(int i), i = 5
~Test(), mValue = 5
t.value() = 5
~Test(), mValue = 5

编译器的行为

  • 编译器会尽力尝试让源码通过编译

"5" 这个立即数默认为 int 类型,怎么可以赋值给 t 对象呢! 现在就报错吗?不急,我看看有没有转换构造函数! OK, 发现 Test 类中定义了 Test(int i),可以进行转换,默认等价于: t = Test(5) ,生成临时对象初始化 t ;

t = 5; <--> t = Test(5);

  • 编译器尽力尝试的结果是隐式类型转换
  • 隐式类型转化

    • 会让程序以意想不到的方式进行工作
    • 是工程中 bug 的重要来源

编程实验: 隐式转换的弊端

#include <iostream>

using namespace std;

class Test
{
private:
    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(5);
    
    t = t + 10;             // 注意这里!
    
    cout << "t.value() = " << t.value() << endl;
    
    return 0;    
}
输出:
t.value() = 15

应用层分析:
类中重载了 + 操作符 Test operator + (const Test& p) , 可是右操作数参数类型为一个对象,为什么使用 整型值常量 可以进行相加操作呢? 是不是笔误? 编译器为什么没有报错呢?

实质分析:
t = t + 10; <--> t = t + Test(10); 编译器进行了隐式类型转换。

结论:
t = t + 10; t = 5; 尽量不使用这样的写法。可能笔误带来的书写,而编译器认为这种行为合法而导致问题。

  • 工程中通过 explicit 关键字杜绝编译器的尝试转换
  • 转换构造函数被 explicit 修饰时只能进行显示转换

    • 转换方式

      • static_cast<ClassName>(value);
      • ClassName(value);
      • (ClassName)value; // 不推荐

编程实验: explicit 尝试

#include <iostream>

using namespace std;

class Test
{
private:
    int mValue;
public:
    Test()
    {
        cout << "Test()" << endl;
        mValue = 0;
    }
    explicit Test(int i)
    {
        cout << "Test(int i), i = " << i << endl;
    
        mValue = i;
    }
    Test operator + (const Test& p)
    {
        Test ret(mValue + p.mValue);
        
        return ret;
    }
    int value()
    {
        return mValue;
    }
    ~Test()
    {
        cout << "~Test(), mValue = " << mValue << endl;
    }
};

int main()
{
    Test t;
    
    t = static_cast<Test>(5);
    //t = Test(5);
    //t = (Test)5;
    
    cout << "t.value() = " << t.value() << endl;
    
    return 0;    
}
输出:
Test()
Test(int i), i = 5
~Test(), mValue = 5
t.value() = 5
~Test(), mValue = 5

分析:
都会生成临时对象初始化 t

小结

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

以上内容参考狄泰软件学院系列课程,请大家保护原创!


TianSong
737 声望139 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧