在日常的编码生活中,程序员时刻与变量的申明,初始化,赋值,运算交互。数据类型有很多种,在不同的编程场景中用到不同的数据类型。在不同类型的数据的交互中,难免遇到类型转换。本文通过C++的类型转换来探讨一下数据类型转换的方式,以及在编码中遇到的问题。
程序代码中充斥最多的就是隐式转换。隐式转换在程序编译时编译器根据编译规则匹配的转换方式。常见隐式转换分为两种方式,一种是表达式类型的,另一种是函数式的。
int a = 1; float b = a; char c = 'a'; b = c + a; class A {}; class B{public: B(A* a){}}; A *a = new A(); B *b = a;
在隐式转换过程中通常遵循如下条列:
1、在表达式里,如果有比int型小的类型,一般都转换为更大的类型
2、在初始化表达式里,都将右边的值转换为初始化数据类型。在赋值表达式里一般将右边的值转换为左边值的类型。
3、在bool 表达式里,将值转换为bool类型
4、在有操作符算数表达式里,不同的数据类型将被转换为同一类型。
在有操作符的表达式里,问题最多的就是unsigned 和signed 类型之间的转换,当unsigned类型长度大于或者等于signed类型的时候,signed类型转换unsigned类型。当两种类型长度一样的时候,signed类型如果是负数的话,这时候对这两个变量进行运算的时候,值是没意义的。
显示转换分为两类,一种是C风格式的,另一种是函数式的。
int num = 2; float PI = 3.14; num = (int)PI; num = int(PI);
#include "stdio.h" #include "string.h" typedef struct { int age; int* pFood ; }Dog; typedef struct{ int age; char* pName; }Duck; int main() { Duck duck; duck.pName = "Duck"; duck.age = 10; Dog* dog = (Dog*)&duck; printf("The dog age is:%d\n", dog->age); printf("The dog food is:%s\n", dog->pFood); return 0; }
例一中将float类型显示的转换为int类型。例二中将一个结构体转换为另一种结构体。因此在在打印中就出现了违背常识的狗的食物变为鸭子。
隐式转换的坑防不胜防,在隐式转换上少出错的方式只能是深入理解程序语言自己的编译规则。知道的背后的秘密也多,就能更好的运用。对于显示转换, 是通过自己手动的去转换数据类型,是建立在我们知道这种转换方式是我们自己主动去转换的。对于结果的话是可预测的,对于看代码的人也明白发生了什么。C++为何提出自己的转换方式呢?因为显示转换方式,编译阶段是发现错误的。所以问题就遗留到运行时发生崩溃。因此C++引入了自己的三种类型方式。
动态转换方式是通过动态模板函数的方式转换类型。动态转换方式只能转换从在继承关系的对象指针,或者引用。如果是从子类转向父类,转换结果是安全的。如果是父类转向子类,且父类没有虚函数的情况下,编译错误。如果有虚函数,转换结果为0。
#include <iostream> using namespace std; class Base { public: int x; }; class Derived : public Base { public: int y; int z; }; int main() { Base* b = new Base(); b->x = 10; Derived* d = new Derived(); d->x = 20; d->y = 30; d->z = 40; Base *b1 = dynamic_cast<Base*>(d); // Derived* d1 = dynamic_cast<Base*>(b); cout<<"b1->x is:"<<b1->x<<endl; cout<<"b1->x address is:"<<&b1->x<<endl; cout<<"d->x address is:"<<&d->x<<endl; cout<<"d->y address is:"<<&d->y<<endl; cout<<"d->z address is:"<<&d->z<<endl; }
#include <iostream> #include <exception> using namespace std; class Base{ public: virtual void Phony() { } }; class Derived : public Base { public: int a; }; int main() { Base *b = new Base(); Derived *d = new Derived(); Base b1; try{ d = dynamic_cast<Derived*>(b); if(d==NULL){ cout<<"Error"<<endl; } }catch(exception& e) { cout<<"exception is :"<<e.what()<<endl; } return 0; }
static_cast这种转换方式在安全转换的时候没有问题,在非安全的转换方式下,也能转换,不过转换的时候如果数据段不能填充目标转换类型的时候,将会为不足的数据段填充。
#include <iostream> #include <exception> using namespace std; class Base { public: int x; }; class Derived : public Base{ public: int y; int z; }; int main() { Base* b = new Base(); b->x = 10; Derived *d = new Derived(); d->y = 20; d->z = 30; try{ d = static_cast<Derived*>(b); cout<<"the d->x is:"<<d->x<<endl; cout<<"the d->y is:"<<d->y<<endl; cout<<"the d->z is:"<<d->z<<endl; }catch(exception& e){ cout<<"the exception is:"<<e.what()<<endl; } }
reinterpret_cast这种转换类型可以将任何类型的数据转换为其他类型的数据。不做检测,只是按内存数据布局将数据从一个数据块按字节拷贝到另一个数据块块。这种转换是不安全的。类似于c语言里的memcpy函数。这种转换方式及其不安全,所以不推荐用。
const_cast 这种转换可以将常量转换为动态变量返回。
#include <iostream> #include <string.h> #include <exception> using namespace std; int main() { const char* str = "hello,world"; try{ char* newStr = const_cast<char*>(str); cout<<newStr<<endl; for(int i =0; i <strlen(newStr) ; ++i) { newStr[i] = 'x'; } cout<<newStr<<endl; }catch(exception& e) { cout<<"the exception is:"<<e.what()<<endl; } }
将常量转换为变量,但是改变转换过来的变量的值,还是会导致崩溃。这是因为常量放在常量区域,是不可改变的。
参考:1、http://www.cplusplus.com/doc/tutorial/typecasting/
2、C++ Primer