数据类型转换
自动类型转换
自动类型转换存在的意义:最大程度的保留你计算所得数据的完整信息。
例如:
#include <iostream>
int main()
{
float i = 90, g = 0;
int j = 8, k = 0; // 一开始赋初值,可以极大程度上避免该变量中一开始存在无效数据
g = i / j;
k = i / j; // i / j部分发生了自动类型转换,结果转换为了专用更高字节的float类型
std::cout << k << std::endl; // k = 11
std::cout << g << std::endl; // g = 11.25
}
其中i/j的结果是90/8=11.25是个float型数据(float=max(float,int))。但是为什么k=11,g=11.25呢?
这又进行了一次类型转换,不过这一次进行的是“强制类型转换“:
K的数据类型是int型,i/j结果的类型是float型(和i的类型一样),我们知道signed float为8字节数据其中包含符号位,小数位,指数位;但是int仅由整数位,因此当我们将float赋值给int时,不可避免地会损失写数据。比如:
上述题目中,我们的结果本应为11.25,但是程序中k值为11,因此我们损失了小数位。
注意:我们在自动类型转换中,遵循“向高字节数据类型转换为了保存更多信息“的规则,我们遵循如下转换表:
C风格的强制类型转换(C语言中的显式类型转换)
强制类型转换是把变量从一种类型转换为另一种数据类型。例如,如果您想存储一个 long 类型的值到一个简单的整型中,您需要把 long 类型强制转换为 int 类型。您可以使用强制类型转换运算符来把值显式地从一种类型转换为另一种类型,如下所示:
(type_name) expression
请看下面的实例,使用强制类型转换运算符把一个整数变量除以另一个整数变量,得到一个浮点数:
#include <stdio.h>
int main()
{
int sum = 17, count = 5;
double mean;
mean = (double) sum / count;
printf("Value of mean : %f\n", mean );
}
当上面的代码被编译和执行时,它会产生下列结果:
Value of mean : 3.400000
C++风格的强制类型转换
C++为了规范C中的类型转换,加强类型转换的可视性,引入了四种强制类型转换操作符:static_cast, reinterpret_cast, const_cast, dynamic_cast ,他们基本上都是模板类。转换格式如下:
variable2 = static_cast<data type>(variable1)
四种强制类型转换符的用法
Static_cast:
它用于非多态类型的转换(静态转换),对应于C中的显式类型转换。该转换在编译时完成,和C风格的类型转换相似,不过要注意下面几点:
1. 不能在没有派生关系的两个类类型之间转换,例如:整形和整形指针之间的转换,虽然二者都是四个字节,但他们一个表示数据,一个表示地址,类型不相关,无法进行转换。
2. 不能去除掉原有类型的类型修饰符,例如const,volatile,__unaligned,例如:原来的值被声明为”volatile型”,那么静态转换static_cast是不能去除这个变量是volatile属性的本质。
3. 转换对象时由于没有动态类型检查,所以由基类对象转换成派生类对象的时候存在安全隐患
附加:何为动态类型检查?
类型检查指验证操作接收的是否为合适的类型数据以及赋值是否合乎类型要求。最自然的方式是认为检查发生在运行时,即当涉及到具体的数据值时,即动态类型检查(即运行时检查)。
#include <iostream>
void main()
{
//C中的方式
int i = 10;
double d1 = i;//隐式类型转换,将int型变量隐式转换为double型变量——操作正确
int *p = i;//无法隐式类型转换,只能强制类型转换——操作错误
int *p = reinterpret_cast<int*>(i); // 可以使用C++中的暴力转换的方式——操作正确(但这样的操作毫无意义)
int *p = (int*)i; // 无法转换,会报错——错误操作
//C++中的方式
double d2 = static_cast<double>(i); // 静态显式类型转换
//相当于创建一个static_cast<double>类型的匿名对象赋值给d2
int* p2 = static_cast<int*>(i);//无法转换,会报错
}
Reinterpret_cast:中文意思为“重新解释定义这个变量的类型“
可将一种类型转换成另一种不相关类型,对应C中的强制类型转换,处理无法进行隐式转换的情况,强制类型转换有时可以很暴力的处理一些问题 :
#include<iostream>
using namespace std;
void Fun(int s)
{
cout << s << endl;
}
typedef void (*FUNC)(); // FUNC代表指向函数的指针类型的新名称
int main()
{
FUNC pf = reinterpret_cast<FUNC>(Fun); // 将Fun函数强制转换为无参的函数
return pf(); // 但是用户为赋于Fun函数形参s一个确定的值,那么此时s可能为一个无效值,即s的大小是随机的
}
虽然我们通过这种BUG的方式转换函数指针,但是这样的代码是不可移植的,而且有时会产生不确定的结果,所以不建议这样来用
如此处输出的s的值就为一个随机值,虽然用户在外部未传参,但是该函数在调用时会创建形参,该形参未初始化,自然是随机值
Const_cast:
const_cast 运算符用于修改类型的 const / volatile 属性。除了 const 或 volatile 属性之外,目标类型必须与源类型相同。这种类型的转换主要是用来操作所传对象的 const 属性,可以加上 const 属性,也可以去掉 const 属性。
#include<iostream>
using namespace std;
int main(){
const int a = 1;
int * b = const_cast<int*>(&a);
*b = 2;
cout << &a <<endl;
cout << b << endl;
cout << a << endl;
cout << *b << endl;
}
结果为:
Dynamic_cast:
主要用于继承指针的强制类型转换:
classB
{
public:
int m_iNum;
virtual void foo();
};
classD:publicB
{
public:
char* m_szName[100];
};
void func(B* pb)
{
D* pd1=static_cast<D*>(pb);
D* pd2=dynamic_cast<D*>(pb);
}
在上面的代
码段中,如果 pb 指向一个 D 类型的对象,pd1 和 pd2 是一样的,并且对这两个指针执行 D 类型的任何操作都是安全的;但是,如果 pb 指向的是一个 B 类型的对象,那么 pd1 将是一个指向该对象的指针,对它进行 D 类型的操作将是不安全的(如访问m_szName),而 pd2 将是一个空指针。
注意:B 要有虚函数,否则会编译出错;static_cast则没有这个限制。
这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。
表达式z=i++或z=++i中,运算的先后顺序
#include <iostream>
int main()
{
int z = 0, k = 0, g = 0;
int i = 9;
z = i++; // 先进行赋值运算,后进行自加1运算
i = 9;
k = ++i; // 先进行自加1运算,后进行赋值运算
i = 9;
i++; // 如果单独执行i++或++i,效果一样,但是一旦自加运算与其他运算复合,那就必须考虑运算优先级的问题了
g = i;
std::cout << g << std::endl; // g = 10
std::cout << z << std::endl; // z = 9
std::cout << k << std::endl; // k = 10
}
我们可以记住一个普遍的规则:
当i++或++i单独执行时,i均执行自加运算,没有什么区别,不用考虑什么运算优先级,因为这里只有一个单独的i自加运算。
但是如果自加运算i++或++i与其他运算复合,那么遵循“谁在前先算谁“——以z=z/++i为例,++在前,因此先执行自加运算i=i+1,后执行z/=i的运算。