### 数据类型转换

在开发应用程序过程中，经常需要进行类型转换。例如，将整型转换为实型，将实型转换为整型，将字符串转换为数值类型，将数值类型转换为字符串类型等等。但是类型转换是一项危险的操作，它可能导致数据的部分丢失，这也成为了程序产生逻辑错误的主要原因之一。下面我们将讨论常用数据类型转换的方法、过程以及各种转换方法可能产生的影响。

l 数值类型转换

在介绍数据类型转换之前，我们来分析一下下面的语句。

int nLen = 5; //定义一个整型变量，初始化为 10

nLen = 10.59 + 2; //为整型变量赋值

执行上面的语句，变量 nLen 的值为多少呢？通过测试，nLen 的值为 12。10.59 是一个 double 类型的文字常量，2 是一个 int 类型的文字常量，他们是不同数据类型的数值。C++在进行不同数值类型的运算时提供了一个数值转换规则，以便将两个不同的数据类型转换为相同的数据类型。数值转换规则是：小的数值类型（表示的数据范围小）被转换为大的数值类型（表示的数据范围大），防止精度的丢失。在执行“nLen = 10.59 + 2;”语句时，整数 2 被提升为 double 类型参与加法运算，结果为 12.59。下一步执行赋值操作，将结果 12.59 赋值为 nLen，由于赋值语句左右两端的类型不同，如果可能（数据类型兼容），编译器会将右边操作数的类型转换为左边操作数的类型再进行赋值操作。在本例中，会将 double 类型转换为 int 类型，这种转换会自动截取小数部分，而不对其进行四舍五入。因此 nLen 的结果为 12。由于这种转换会导致数据的部分丢失，因此 C++编译器会提示一个警告信息。

warning C4244: '=' : conversion from 'const double' to 'int', possible loss of data

下面我们介绍在数值计算（加减乘除等操作）过程中一些常用的数值转换规则。

q 如果两个操作数有一个操作数的类型为 long double，则另一个操作数无论何种类型，均被转换为 long double 类型。

q 如果两个操作数有一个操作数的类型为 double，而另一个操作数不是 long double 类型，则另一个操作数被转换为 double 类型。

例如，进行 10.59 + 2 的运算，结果为 double 类型。

q 如果两个操作数的类型都不是 double（包括 long double）类型，而一个操作数的类型是 float，则另一个操作数被转换为 float 类型。

q 如果两个操作数都是整数，编译器将所有小于 int 类型的操作数提升为 int 类型。然后继续进行比较，如果有一个操作数类型为 unsigned long，则另一个操作数也被转换为 unsigned long。如果两个操作数都不是 unsigned long，而其中一个操作数是 long 类型，则另一个操作数也被转换为 long 类型（这里的一个例外情况是如果一个操作数是 long ，另一个操作数是 unsigned int，那么两个操作数都将转换为 unsigned int 类型）。

在编写程序时，如果不希望遵守 C++的数值转换规则，可以通过强制类型转换将某一个操作数转换为指定的类型。那么何时需要使用强制类型转换呢？分析下面的语句。

double dbRate = 10 / 3;

在执行“double dbRate = 10 / 3;”语句后变量 dbRate 的值是多少呢？10 除以 3，理论上为 3.33333…，但是由于 10 和 3 均是整型，其运算结果也为整型，因此 10 / 3 在计算机中的结果为 3，将结果 3 赋值给 double 类型，dbRate 的结果为 3.0000…。这就是采用了 C++默认的数值转换规则，它通常不符合我们的逻辑（我们的预期结果是 3.33333…）。为此就需要强制类型转换替换 C++默认的数值转换规则。在 C++中有两种强制类型转换方式，一种是使用 C 语言提供的原始的强制类型转换，另一种是 C++语言引入的新的强制类型转换。下面介绍 C 语言提供的强制类型转换，有关 C++语言引入的新的强制类型转换，我们在后面内容中进行介绍。C 语言提供的强制类型转换格式有两种，分别为：

数据类型 (表达式)

(数据类型)表达式

例如，将一个整型转换为 double 类型，可以写为：double(10)或者(int)10。在计算 10 / 3 时，为了获取精确的结果，可以将 10 或 3 转换为 double 类型，当然也可以将 10 和 3 都转换为 double 类型。例如：

double dbRate = (double)10 / 3;

这样，dbRate 的结果为“3.33333…”。

l 静态转换(static_cast)

静态转换是 C++语言提供的一种强制类型转换方式。它使用 static_cast 关键字来实现强制类型转换。下面的代码使用 static_cast 关键字将整型转换为 double 类型。

double dbRate = static_cast<double>(10) / 3;

在程序中合理的使用强制类型转换可以提高效率。分析下面的代码：

int nLen = 10.5 + 5;

编译器首先将 5 转换为 double 类型，再执行加法运算，最后将结果（double 类型）转换为 int 类型。如果对 10.5 进行强制类型转换，转换为 int 类型，则可以减少类型转换的次数。例如：

int nLen = static_cast<int>(10.5) + 5;

代码首先将 10.5 转换为 int 类型，然后指向加法运算，最后将结果（int 类型）赋值为 nLen。这样就减少了一次类型转换。

在编写程序代码时，如果程序不需要在 C 语言的环境中运行，建议使用 C++形式的强制类型转换。因为 C++形式的强制类型转换有一些安全性的限制。例如：使用 C 语言的强制类型转换能够将整型指针转换为结构体类型指针，而 static_cast 则不允许进行这样的转换。

int* nLen;

Student* girl = (Student*)nLen; //能够进行类型转换进行编译

Student* boy = static_cast<Student*>(nLen); //错误的语句，不能够进行类型转换

l 动态转换(dynamic_cast)

动态转换也是 C++语言提供的一种强制类型转换方式。与静态转换不同的是在转换前它能够进行类型检查。动态转换通常用于一个类对象指针转换为另一个类对象指针。如果源指针类型与目标指针类型不兼容，则转换的结果为 NULL。程序中可以通过检测结果是否为 NULL 来判断强制类型转换是否成功。在 C++中使用 dynamic_cast 关键字进行动态转换。动态转换只能对 void*（无类型指针）类型或者类对象指针进行转换，并且类中必须包含有虚方法，而不能对普通的数据类型进行转换。

l 常量转换(const_cast)

常量转换用于将 const 对象转换为非 const 对象。分析下面的语句：

const int MAX_LEN = 100; //定义一个整型常量

int *pLen = &MAX_LEN; //定义一个指针，试图使用常量的地址进行初始化，导致编译错误

上面的语句定义一个整型指针变量，使用整型常量的地址进行初始化，这是不允许的。因为&MAX_LEN 表示一个常量整型指针，它不能够直接赋值给整型指针，需要进行强制类型转换。例如：

const int MAX_LEN = 100; //定义一个整型常量

int *pLen = (int*)&MAX_LEN; //将常量整型指针转换为整型指针，语句能够编译，但不提倡

使用 C 语言提供的强制类型转换能够将常量指针转换为非常量指针。但是在 C++中提供了一个更好的转换方式——常量转换。通过使用 const_cast 能够将 const 对象转换为非 const 对象。例如：

const int MAX_LEN = 100; //定义一个整型常量

int *pLen = const_cast<int*>(&MAX_LEN); //常量转换

相对于 C 语言的强制类型转换，const_cast 是有限制的，这也使得 const_cast 更安全一些。例如，使用 C 语言的强制类型转换能够将一个 int 类型的常量指针转换为 long 类型的指针，而 const_cast 则不允许进行这样的转换。例如：

const int MAX_LEN = 100; //定义一个整型常量

long *pCount = (long*)&MAX_LEN; //将 int 类型的常量指针转换为 long 类型的指针

long *pLen = const_cast<long*>(&MAX_LEN); //不允许进行转换

知识导向：根据常量的定义可知，常量在运行是其值是不能改变的。但是如果通过常量转换，将常量对象转换为非常量对象，利用非常量对象能否间接修改常量的值呢？下面我们做一个试验，试验的目的是检测将常量对象转换为非常量对象后，能够修改常量的值。分析下面的代码：

const int MAX_LEN = 100; //定义一个整型常量

int *pLen = const_cast<int*>(&MAX_LEN); //定义一个整型指针，初始化为常量的地址

*pLen = 200; //修改指针指针指向的目标数据

int nValue = MAX_LEN; //定义一个变量，初始化为常量值

cout << "nValue 的值："<< nValue <<endl; //输出变量 nValue

上述代码中利用整型指针 pLen 获取常量的地址，修改指针指向的目标数据，这样应该导致“常量的值发生改变”。但是，最后的输出结果 nValue 的值仍为 100。难道常量值没有发生改变？通过跟踪代码，在调试窗口中发现常量值发生了改变，如图 1 所示。

常量的值发生了改变

图 1 调试窗口

既然常量 MAX_LEN 的值发生了改变（值为 200），那么将其赋值给 nValue 变量后，nValue 的值怎么还是 100，而不是 200 呢？为了查看究竟，将上述代码转换为汇编代码，如图 2 所示。

图 2 汇编窗口

从图 2 中可以发现，编译器将语句“int nValue = MAX_LEN;”翻译成汇编语言为“mov dword ptr [ebp-0Ch],64h”，即将十六进制数 64h（十进制数 100）传递到“ebp-0CH”地址上。那么“ebp-0CH”地址表示的一定是变量 nValue 的地址。利用内存窗口查看变量 nValue 的地址，如图 3 所示。

图 3 内存窗口 1

从图 3 中可以发现变量 nValue 的地址为“0013FF74”，下面观察“ebp-0CH”的地址，如图 4 所示。

图 4 内存窗口 2

这样，语句“mov dword ptr [ebp-0Ch],64h”就实现了将 100 赋值给变量 nValue，最后 nValue 的值为 100。编译器将“int nValue = MAX_LEN;”翻译成汇编语言为“mov dword ptr [ebp-0Ch],64h”，因为 MAX_LEN 是常量，原则上是不能改变常量值的，因此编译器在编译时遇到常量，将其直接翻译为一个常数，而不进行寻址操作。这就是变量 nValue 的值仍为 100 的原因。

l 重解释转换(reinterpret_cast)

重解释转换能够将任何指针类型转换为其他的指针类型，这种转换方式是一种不安全的转换方式。在应用程序中尽量少使用重解释转换。在 C++中使用 reinterpret_cast 关键字实现重解释转换。下面的语句利用重解释转换将一个指针转换为无符号整数指针。

char *pszText = "mrkj"; //定义一个字符指针

unsigned int* pAddr = reinterpret_cast<unsigned int*> (pszText); //将字符指针转换为无符号整数指针

char* pszCaption = reinterpret_cast<char*>(pAddr); //将无符号整数指针转换为字符指针

在使用重解释转换时，通常转换后的指针是不能够按照它的数据类型使用的（当然，在特殊情况是可以使用的），例如本例中的 pAddr，只有在将其恢复到原来的类型时才具有意义。

在程序中使用 reinterpret_cast 是危险的、不明智的，因此建议开发人员少使用，谨慎使用 reinterpret_cast，但不是禁用 reinterpret_cast。

注意：重解释转换 reinterpret_cast 不能构将常量指针转换为其他的非常量指针，这需要使用常量转换 const_cast 来实现。

l 数值类型与字符串之间的转换

在应用程序中，经常涉及到数值类型与字符串类型之间的转换。例如，将两个字符串转换为数值类型进行数学运算，将两个数字连接为一个字符串等。为了方便进行数值和字符串间的转换，C++提供了相关的转换函数。

q 字符串转换为整数

C++语言提供了 atoi 函数将字符串转换为整数，该函数语法如下：

int atoi(const char *string);

参数说明：

string：表示待转换后的字符串。

返回值：如果函数执行成功，返回值为字符串转换后的整数，如果参数 string 不能够转换为字符串，返回值为 0。如果参数 string 转换后的数值溢出，返回值未知。

下面利用 atoi 函数将两个字符串转换 为整数，再进行乘法运算。

char *pszHeight = "100"; //字符串变量

char *pszWidth = "20"; //字符串变量

int nHeight = atoi(pszHeight); //将字符串转换为整数

int nWidth = atoi(pszWidth); //将字符串转换为整数

int nArea = nHeight * nWidth; //将两个整数相乘

注意：在程序中使用 atoi、atof 等类型转换函数前，需要引用 stdlib.h 头文件。

q 字符串转换为实数

C++语言提供了 atof 函数将字符串转换为整数，该函数语法如下：

double atof(const char *string);

参数说明：

string：表示待转换后的字符串。

返回值：如果函数执行成功，返回值为字符串转换后的实数，如果参数 string 不能够转换为字符串，返回值为 0.0。如果参数 string 转换后的数值溢出，返回值未知。

下面利用 atof 函数将两个字符串转换为实数，再进行加法运算。

char *pszHeight = "48.45"; //字符串变量

char *pszWidth = "120.87"; //字符串变量

double dbHeight = atof(pszHeight); //将字符串转换为实数

double dbWidth = atof(pszWidth); //将字符串转换为实数

double dbArea = dbHeight + dbWidth; //将两个实数进行加法运算

q 整数转换为字符串

C++语言提供了 itoa 函数将整数转换为字符串，该函数语法如下：

char *itoa(int value, char *str, int radix);

参数说明：

value：表示待转换的整数。

str：表示一个字符指针，用于存储函数转换后的字符串。

radix：表示基数，范围 2…36。通常为 10，即采用十进制转换。

返回值：函数返回一个指向 str 参数的字符指针。

下面利用 itoa 函数将一个整数转换为字符串，并且输出字符串。

int nDays = 365; //定义一个整型变量

char pszText[128] = {0}; //定义一个字符数组，用于存储字符串

itoa(nDays, pszText, 10); //将整数转换为字符串

cout << pszText << endl; //输出字符串

q 实数转换为字符串

C++语言提供了 fcvt 函数将实数转换为字符串，该函数语法如下：

char *fcvt(double value, int count, int *dec, int *sign);

参数说明：

value：表示待转换的实数。

count：表示小数点后的位数，即精度。

dec：是一个整型指针，记录小数点的位置。

sign：是一个整型指针，表示数字的符号，0 为正数，1 为负数。

返回值：如果函数执行成功，返回值是一个字符指针，否则为 NULL。

下面利用 fcvt 函数将一个实数转换为字符串。

double dbPI = 3.1415926; //定义一个 double 类型变量 dbPI

int nDec; //定义一个整型变量

int nSign; //定义一个整型变量

char *pszText = fcvt(dbPI, 2, &nDec, &nSign); //将 dbPI 转换为字符串