C++中类型转换详解

0. 相关概念

隐式转换: 不需要进行声明,系统根据程序的需要而进行的自动转换。
显式转换: 通过强制转换运算符强行进行的转换,由程序员在写程序过程中显式声明。


1. 内置类型中的自动类型转换

1.1 初始化和赋值时进行的转换

  在C++中允许将一种基础数据类型(如char、int、double等)赋给另一基础类型。此时系统将自动的进行转换。将一个值赋给取值范围更大的类型通常不会导致什么问题,但赋给取值范围更小的类型时可能会带来麻烦。
潜在转换问题
 

1.2 表达式中的转换

  表达式中出现的转换主要分为两种,一种是自动转换,即该类型出现时即进行转换,自动转换常见于整型提升;一种是与其他类型同时出现在表达式中时将被转换。
  整型提升: 在计算表达式时,C++将bool、unsigned char、signed char和short转换为int,这些转换被称为整型提升。因为int一般是计算机最自然的类型,这意味着计算机使用这种类型时运算速度可能最快,除此之外的整型提升还有unsigned short,若short比int短,则unsigned short被转换为int,否则转换为unsigned int,这是为了保证整型提升过程中不会损失数据。
例如在以下语句中:

short x1 = 20;
short x2 = 10;
short y = x1 + x2;

在执行第三行时,x1和x2将先被转换为int类型,进行计算后再将结果转换为short类型赋给y。
  当运算涉及两种类型时,编译器通过查校验表来确定在算术表达式中执行的转换,以下为C++11版本的校验表:
校验表
 

1.3 以{ }方式初始化时的转换(C++11)

  C++11将使用{ }初始化的方式称为列表初始化,相较于使用"="号进行初始化的方式,列表初始化对类型转换的要求更严格,列表初始化不允许缩窄,即变量类型可能无法表示赋给它的值。例如,不允许将浮点型转换为整型。在不同的整型之间转换或将整数类型转换为浮点型可能被允许,条件是编译器知道目标变量能够正确的存储赋给它的值。例如,可将int值赋给long变量,因为long总是至少与int一样长。
 

1.4 传递函数参数时的转换

  传递参数时的类型转换通常由C++函数原型控制。C++自动地将传递的值转换为原型中指定的类型,条件是两者都是算术类型。但函数重载时可能导致二义性,因此不允许某些自动强制类型转换。
例如在以下程序中

double mySquare(double val)
{
    return val * val;
}
int main()
{
    int a = 10;
    double b = mySquare(a);
    std::cout << b;
    return 0;
}

程序将自动将a转换为double类型,但若将mySquare()函数重载为

double mySquare(double val)
{
    return val * val;
}
double mySquare(float val)
{
    return val * val;
}
int main()
{
    int a = 10;
    double b = mySquare(a);
    std::cout << b;
    return 0;
}

此时将报错,有多个重载函数实例与参数列表匹配。


2.类之间的自动类型转换

2.1 内置类型转换为类类型

  C++中提供内置类型到类类型的自动转换,但条件是需要给类提供一个仅有一个参数的构造方法。

class myClass
{
public:
    myClass(int a){};
};
int main()
{
    myClass A = 10;            //合法的,因为类A中提供了含一个int参数的构造方法
    return 0;
}

此时转换过程为先调用构造方法myClass(10)创建一个临时对象temp,再将temp对象复制到我们声明的对象A中,最后销毁temp临时对象。
但是可通过explicit关键字来关闭这种显示转换,例如在上例中给构造方法加入explicit关键字:

class A
{
public:
    explicit A(int a){};
};
int main()
{
    A a = 10;            //非法的,不允许隐式转换
    return 0;
}

 

2.2 类类型转换为内置类型

  想要将类类型转换为内置类型,需要在类中定义一个转换函数,转换函数语法如下:

operator type-name();

例如将类myClass转换为int类型的转换函数如下:

#include<iostream>
class myClass
{
public:
    operator int()
    {
        return 10;
    };
};
int main()
{
    myClass A ;   
    int a = int(A);         //a=10
    return 0;
}

 

2.3 类类型之间的转换

无派生关系的类之间的转换: 无派生关系的类之间的转换可使用上述3.1和3.2之中的转换方法:

class myClassA
{   
};
class myClassB
{
public:
    myClassB(const myClassA& A) {};
    operator myClassA() {
        myClassA a;
        return a;
    }
};
int main()
{
    myClassA A ;   
    myClassB B = A;         //myClassA类到myClassB类的转换
    myClassA C = myClassA(B);     //myClassB类到myClassA类的转换
    return 0;
}

派生类与基类之间的转换: 假设High和Low是两个类,Low是High的可访问基类(直接或间接)时,尝试将High类对象赋给Low类对象将发生自动类型转换,但将Low类对象赋给High类对象将报错,此时将只能使用强制类型转换。同理可以将基类指针指向派生类对象,而将派生类指针指向基类对象是非法的。

class myClassA
{   
};
class myClassB :public myClassA
{
};
int main()
{
    myClassB B1;
    myClassA A1 = B1;      //合法的,由派生类对象B1到基类对象A1的转换
    myClassA A2;
    myClassB B2 = A2;     //不合法的,并不提供基类对象A2到派生类对象B2的自动转换
    return 0;
}

3.强制类型转换

  C++允许通过强制转换机制来显式的进行类型转换。强制类型转换并不修改变量本身,而是创建一个新的指定类型的值。可以在表达式中使用这个值。

3.1 来自于C语言的强制类型转换

  C语言式的强制类型转换通用格式有两种:

(typename)value
typename(value)

前一种格式来自于C语言,后一种格式是纯粹的C++,新格式的目的是让强制类型转换就像是函数调用。这样内置类型的强制类型转换就像是为用户定义的类设计的类型转换。例如,将变量thorn强制转换为long类型可使用下列表达式的一种:

(long)thorn
long(thorn)

 

3.2 强制类型转换运算符

  在C++的创始人Bjarne Stroustrup看来,C语言中强制类型转换太过松散。例如在下列代码中:

#include<iostream>
struct Data
{
    double data[200];
};
struct Junk
{
    int junk[100];
};
int main()
{
    Data d{ 2.5e33,3.5e-19, 20.2e32};   //将d.data的前三个元素初始化
    char* pch = (char*)(&d);             //将Data*类型转换为char*类型
    char ch = char(&d);                  //将Data*类型转换为char类型
    Junk* pj = (Junk*)(&d);             //将Data*类型转换为Junk*类型
    std::cout << *(pj->junk);             //结果不确定
    return 0;
}

上述三种类型转换在C语言中都是允许的,但其转换显然并无意义。因此对于这种情况,Stroustrop采取的措施是:更严格的限制允许的类型转换,并添加4个类型转换运算符,使转换过程更规范,接下来将详细介绍这4个强制类型转换运算符。

1. dynamic_cast运算符
  该运算符的用途是用于在类层次结构中进行向上转换(由于is-a关系,这样的类型转换是安全的),而不允许其他转换。例如High和Low是两个类,而ph和pl分别为High* 和Low*,则仅当Low是High的可访问基类(直接或间接)时,下面的语句才将一个Low*指针赋给pl:

pl = dynamic_cast<Low*> (ph);

否则,该语句将空指针赋给pl,通常,该运算符的语法如下:

dynamic_cast<type-name> (expression)

 
2. const_cast运算符
  该运算符只用于执行一种用途的转换,即改变值为const或volatile,如果类型的其他方面也被修改,则该类型转换将出错。例如在下列语句中

High bar;
const High* pbar = &bar;
...
High* pb = const_cast<High*> (pbar);             //合法的,删除了const标签
Low* pl = const_cast<Low*> (pbar);               //非法的,尝试把const High*类型转换为Low*类型

提供该运算符的原因是:有时我们需要这样一个值,它大多数时候是常量,但有时又是可以修改的。在这种情况下我们可以将这个值声明为const,并在需要修改它的时候,使用const_cast。通常,该运算符语法与dynamic_cast相同:

const_cast<type-name> (expression)

 
3. static_cast运算符
  该运算符仅当type-name可被隐式转换为expression所属的类型或expression可被隐式转换为type_name所属的类型时,该转换才是合法的,否则将出错。该运算符可用来将double转换为int,将枚举值转换为int类型等各种数值操作,该运算符语法同其他类型转换运算符:

const_cast<type-name> (expression)

 
4. reinterpret_cast运算符
  reinterpret_cast运算符用于天生危险的类型转换。它不允许删除const,但会执行其它危险的操作,使用reinterpret_cast运算符可以简化对这种行为的跟踪工作。但reinterpret并不支持所有的类型转换。例如,可以将指针类型转换为足以存储指针表示的整型,但不能将指针转换为更小的整型或浮点型。另一个限制是,不能将函数指针转换为数据指针。该运算符语法同其他类型转换运算符:

reinterpret_cast<type-name> (expression)

总结

  在写程序时应当多使用显性转换来代替隐式转换,使用C++中提供的强制转换运算符来代替C语言方式的强制转换。这样可提高程序的健壮性和安全性,在多人参与的大型开发项目中,自由便意味着可能有预料之外的情况出现,Coding一时爽,Debug火葬场。

  • 7
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值