在C++中,两个类型相关的定义是:这两个类型之间可以相互转。

一、隐式转换

C++不把两个不同类型的值加在一起,而是提供了一组转换规则,以便在执行算数操作前将两个操作数转换为同一种类型。这类不需要程序员介入而直接执行的转换称为隐式类型转换。

C++定义的算术类型之间的内置转换尽可能防止精度损失。编译器在必要时将类型转换规则应用到内置类型和类类型的对象。

1、发生隐式类型转换的情况:

(1)、在混合类型表达式中,其操作数被转换为相同的类型:

  1. int ival;

  2. double dval;

  3. ival >= dval;//ival转换为double

(2)、用作条件表达式被转换为bool类型:

  1. int ibal;

  2. if (ival)      //ival被抓环卫bool型

  3. while (cin)    //ival被抓环卫bool型

条件操作符(?:)中的第一个操作数以及逻辑非(!)、逻辑与(&&)和逻辑或(||)的操作数都是条件表达式。出现在if、while、for和do...while语句中的同样也是条件表达式。

(3)、用一表达式初始化某个变量,或将一表达式赋值给某个变量,则该表达式被转换为该变量的类型:

  1. int ival = 3.14;   //3.14转换为int型

  2. int *ip;

  3. ip = 0;          //0被转换为int型空指针

2、算数转换:

此类隐式转换最常用,它保证在执行操作之前,将二元操作符的两个操作数转换为同一类型,并使表达式的值也具有相同的类型。最简单的转换为整型提升:对所有比int小的整型,包括bool(0或1)、char、signed char、unsigned char、short和unsigned short,如果该类型的所有可能的值都能包容在int内,它们就会被提升为int型,否则,它们将被提升为unsigned int。

有符号与无符号类型之间的转换:

这类转换本质上依赖机器。因为如果表达式使用了无符号数值,那么所定义的转换规则需保护操作数的精度。而unsigned操作数的转换依赖于机器中整型的相对大小。

对unsigned short/int和int/long的转换,如果int/long型足够表示unsigned short/int型的值,九江unsigned short/int转换为int/long型,否则将两个操作数转换为unsigned int/long。

3、指针转换:

使用数组时,大多数情况下数组都会自动转换为指向第一个元素的指针:

  1. int ia[10];

  2. int *ip = ia;  //ia转换为指向数组中第一个元素的指针

例外情况有:

数组用作取地址(&)操作符的操作数或sizeof操作符的操作数时,或用数组对数组的引用进行初始化时。

PS:指向任意数据类型的指针都可以转换为void*类型;整型数值常量0可转换为任意指针类型。

4、转换为bool类型:

算数值和指针值都可以转换为bool类型。对应false和true。

bool型也可以转换为int型,对应为0和1。

5、转换与枚举类型:

C++自动将枚举类型的对象或枚举成员转换为整型,该转换结果可用于任何要求使用整数值的地方。而将enum对象或枚举成员提升为什么类型由机器定义,并且依赖于枚举成员的最大值。

6、转换为const对象:

当使用非const对象初始化const对象的引用时,系统将非const对象转换为const对象。此外,还可以将非const对象的地址(或非const指针)转换为指向相关const类型的指针:

  1. int i;

  2. constint ci = 0;

  3. constint &j = i;

  4. constint *p = &ci;

  5. constint *p = &i;

二、显式转换

即强制类型转换。

命名的强制类型转换符号的一般形式如下:

  1. cast-name<type>(expression);

其中cast-name为static_cast、dynamic_cast、const_cast和reinterpret_cast之一,type为转换的目标类型,而expression则是被强制转换的值。

1、dynamic_cast

dynamic_cast支持运行时识别指针或引用所指向的对象。与其一起使用的指针必须是有效的。

与其它三者不同的是,它涉及运行时类型检查。如果转换到指针类型的dynamic_cast失败,则dynamic_cast的结果是0值;若转换到引用类型的dynamic_cast失败,则抛出一个bad_cast类型的异常。

因此,该操作符一次执行两个操作。它首先验证被请求的转换是否有效,只有转换有效,操作符才实际进行转换。

2、const_cast

该操作符可以转换掉表达式的const性质:

  1. const char *pc_ptr;

  2. char *pc = string_copy(const_cast<char*>(pc_str));

只有使用const_cast才能将const性质转换掉,而且,除了添加与删除const特性,用const_cast符来执行其它任何类型转换,都会引起编译错误。

3、static_cast

编译器隐式执行的任何类型转换都可以用static_cast显示完成:

  1. double d = 97.0;

  2. char ch = static_cast<char>(d);

当需要将一个较大的算术类型赋值给较小的类型时,使用强制转换非常有用。

4、reinterpret_cast

reinterpret_cast通常为操作数的位模式提供较低层次的重新解释。该操作符本质上依赖于机器,因此其安全性应重点考虑。

  1. int *ip;

  2. char *pc = reinterpret_cast<char*>(ip);

程序员应该记得pc所指向的真实对象其实是int型,而并非字符数组。任何假设pc是普通字符指针的应用,都有可能带来未知的运行时错误。

PS:应尽量避免使用强制类型转换,不依赖强制类型转换往往也能写出很好的C++程序。

三、实参类型转换(函数重载)

类型提升或转换适用于实参类型可通过某种标准转换提升或转换为适当的形参类型的情况。

必须注意的一个重点是较小的整型提升为int型。假设有两个函数,一个的形参为int型,另一个的形参则是short型。对于任意整型的实参值,int版本都是由于short版本的较佳匹配,即使从形式上看short型版本的匹配更佳:

  1. void ff(int);

  2. void ff(short);

  3. ff('a');        //char提升为int

四、转换与类类型

我们可以用一个实参调用的非explicit构造函数定义一个隐式转换,当提供了实参类型的对象而需要一个类类型的对象时,编译器将使用该转换。

除了定义类类型的转换外,我们还可以定义从类类型的转换。即,定义转换操作符,给定类类型的对象,该操作符将产生其他类型的对象,而且编译器也将自动应用这个转换。其用途如下:

1、支持混合类型表达式;

2、转换减少所需操作符的数目。

转换操作符有:

转换操作符是一种特殊的类成员函数,它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,在保留字operator之后跟着转换的目标类型:

  1. operator type();

type表示内置类型名、类类型名由类型别名所定义的名字。对任何可作为函数返回类型的类型(除void外)都可以定义转换函数。一般而言,不允许转换为数组或函数类型,转换为指针类型(数据和函数指针)以及引用类型是可以的。

虽然转换函数不能指定返回类型,但是每个转换函数必须显式返回一个指定类型的值。

PS:转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。转换函数一般不应该改变被转换的对象。因此,转换操作符通常应定义为const成员。

1、使用类类型转换

只要存在转换,编译器将在可以使用内置转换的地方自动调用它:表达式中、条件中、实参传值、重载、显示类型中等。

2、类类型转换和标准转换

使用转换函数时,被转换的类型不必与所需要的类型完全匹配。必要时可以在类类型转换之后跟上标准转换以获得想要的类型。

3、只能应用一个类类型转换

类类型转换之后不能再跟另一个列类型转换。如果需要多个类类型转换,则代码将出错。

4、标准转换可放在类类型转换之前

使用构造函数执行隐式转换时,构造函数的形参类型不必与所提供的类型完全匹配。如果需要,在调用构造函数执行类类型转换之前,可将一个标准转换序列应用于实参。

五、转换与继承

主要是基类类型和派生类型之间的转换。

1、派生类到基类的转换

如果有一个派生类型的对象,则可以使用它的地址对基类类型的指针进行赋值或初始化。同样,可以使用派生类型的引用或对象初始化基类类型的引用。

(1).引用转换不同于转换对象

我们可以将派生类型的对象传给希望接受基类引用的函数,这时,引用直接绑定到该对象,实参只是该对象的引用,对象本身未被复制。

(2).用派生类对象对基类对象进行初始化或赋值

对基类对象进行初始化或赋值实际上实在调用函数:初始化时调用构造函数,赋值时调用赋值操作符。正因为存在从派生类引用到基类引用的转换,才使赋值控制成员可用于从派生类对象对基类对象进行初始化或赋值成为可能。

(3).派生类到基类转换的可访问性

像继承的成员函数一样,从派生类到基类的转换可能是也可能不是可访问的,这取决于在派生类的派生列表中指定的访问标号。

PS:要确定到基类的转换是否可访问,可以考虑基类的public成员是否可访问,如果可以,则转换是可访问的,否则,转换是不可访问的。

2、基类到派生类的转换

从基类到派生类的自动转换是不存在的,需要派生类对象时不能使用基类对象,因为基类对象只能是基类对象对象,不能包含派生类型的成员。

当基类指针或引用实际绑定到派生类对象时,从基类到派生类的转换也是存在限制的,编译器确定转换是否合法,只看指针或引用的静态类型。在此情况下,如果知道从基类到派生类的转换是安全的,就可以使用static_cast强制编译器进行转换,或者可以用dynamic_cast申请在运行时进行检查。