C/C++数据类型的转换之终极无惑
程序开发环境:VS2012+Win32+Debug
数据类型在编程中常常遇到。尽管可能存在风险,但我们却乐此不疲的进行数据类型的转换。
1. 隐式数据类型转换
数据类型转换。究竟做了些什么事情呢?实际上。数据类型转换的工作相当于一条函数调用。若有一个函数撰文负责从double转换到int(假设函数是dtoi),则以下的转换语句:
double d=4.48;
int i=d; //报告警告
等价于i=dtoi(d)。
函数dtoi的原型应该是:int dtoi(double)或者是int dtoi(const double&)。有些类型的数据转换时绝对安全的,所以能够自己主动进行。编译器不会给出不论什么警告,如由int型转换成double型。还有一些转换会丢失数据,编译器仅仅会给出警告。并不算一个语法错误,如上面的样例。各种基本数据类型(不包含void)之间的转换都属于以上两种情况。
以上两种不显示指明数据类型的转换就是隐式数据类型转换。
隐式数据类型转换无处不在,主要出如今以下几种情况。
(1)算术运算式中,低类型能够转换为高类型。
(2)赋值表达式中。右边表达式的值自己主动隐式转换为左边变量的类型,并赋值给他。
(3)函数调用中參数传递时。系统隐式地将实參转换为形參的类型后,赋给形參。
(4)函数有返回值时,系统将隐式地将返回表达式类型转换为返回值类型。赋值给调用函数。
编程原则:请尽量不要使用隐式类型转换,即使是隐式的数据类型转换是安全的,由于隐式类型数据转换减少了程序的可读性。
2. 显示数据类型转换
显示数据类型转换是显示指明须要转换的类型。首先考察例如以下程序。
#include <iostream>
using namespace std;
int main(int argc,char* argv[])
{
short arr[]={65,66,67,0};
wchar_t *s;
s=arr;
wcout<<s<<endl;
getchar();
}
由于short int和wchar_t是不同的数据类型。直接把arr代表的地址赋给s会导致一个编译错误:error C2440:“=”:无法从“short[4]”转换为“wchar_t”。
为了解决这样的“跨度较大”的数据类型转换。能够说还用显示的“强制类型转换”机制,把语句s=arr;改为s=(wchar_t*)arr;就能顺利通过编译。并输出:ABC。
强制类型转换在C语言中就已经存在。到了C++语言中能够继续使用。在C风格的强制类型转换中,目标数据类型被放在一堆圆括号里,然后置于源数据类型的表达式前。在C++语言中,同意将目标数据类型当做一个函数来使用,将源数据类型表达式置于一对圆括号里,这就是所谓的“函数风格”的强制类型转换。
以上两种强制转换没有本质差别,仅仅是书写形式上略有不同。即:
(T)expression //C-style cast
T(expression) //function-style cast
可将它们称为旧风格的强制类型转换。在上面的程序中,能够用以下两种书写形式实现强制类型转换:
s=(wchar_t*)arr;
typedef wchar_t* LSPTR; s= LSPTR(arr);
3.C++中新式类型转换
在C++语言中。添加了四中内置的类型转换操作符:const_cast、static_cast、dynamic_cast和reinterpret_cast。
它们具有统一的语言形式:type_cast_operator(expresiion)。
以下分别介绍。
3.1 const_cast
const_cast主要用于解除常指针和常量的const和volatile属性。也就是说,把cosnt type*转换成type*类型或将const type&转换成type&类型,可是要注意,一个变量本身被定义为仅仅读变量。那么它永远是常变量。
const_cast取消的是对间接引用时的改写限制(即仅仅针对指针或者引用),而不能改变变量本身的const属性。如以下的语句就是错误的。
const int i;
int j=const_cast<int>(i);
正确示比例如以下:
void constTest{
const int a=5;
int *p=NULL;
p=const_cast<int*>(&a);
(*p)++;
cout<<a<<endl;//输出6
}
程序正常执行。输出6。
3.2 static_cast
static_cast相当于传统的C语言中那些“较为合理”的强制类型转换,较多的使用于基本数据类型之间的转换、基类对象指针(或引用)和派生类对象指针(或引用)之间的转换、一般的指针和void*类型的指针之间的转换等。
static_cast操作对于类型转换的合理性会作出检查,对于一些过于“无理”的转换会加以拒绝。比如以下的转换:
double d=3.14;
double* p=static_cast<double*>(d);
这是一种非常诡异的转换,在编译时会遭到拒绝。
另外,对于一些看似合理的转换。也可能被static_cast拒绝。这时要考虑别的方法。如以下的程序。
#include <iostream>
using namespace std;
class A{
char ch;
int n;
public:
A(char c,int i):ch(c),n(i){}
};
int main(int argc,char* argv[])
{
A a('s',2);
char* p=static_cast<char*>(&a);
cout<<*p;
getchar();
}
这个程序无法通过编译,就是说,直接将A*类型转换为char*是不同意的。这时能够通过void*类型作为中介实现转换。
改动后的程序例如以下。
void* q=&a;
char* p=static_cast<char*>(&q);
这样,程序就能够通过编译。输出s。可见,假设指针类型之间进行转换。一定要注意转换的合理性。这一点必须由程序猿自己负责。指针类型的转换以为这对原数据实体的内容的又一次解释。
尽管const_cast是用来去除变量的const限定,可是static_cast却不是用来去除变量的static引用。事实上这是非常easy理解的。static决定的是一个变量的作用域和生命周期。比方:在一个文件里将变量定义为static,则说明这个变量仅仅能在本Package中使用;在方法中定义一个static变量。该变量在程序開始存在直到程序结束;类中定义一个static成员,该成员随类的第一个对象出现时出现,并且能够被该类的全部对象所使用。
对static限定的改变必定会造成范围性的影响。而const限定的仅仅是变量或对象自身。
但不管是哪一个限定。它们都是在变量一出生(完毕编译的时候)就决定了变量的特性。所以实际上都是不容许改变的。这点在const_cast那部分就已经有体现出来。
在实践中。static_cast多用于类类型之间的转换。
这时,被转换的两种类型之间一定存在派生与继承的关系。见例如以下程序。
#include <iostream>
using namespace std;
class A{};
class B{};
int main(int argc,char* argv[])
{
A* pa;
B* pb;
A a;
pa=&a;
pb=static_cast<B*>(pa);
getchar();
}
改程序无法通过编译。原因是类A与类B没有不论什么关系。
综上所述,使用static_cast进行类型转换时要注意例如以下几点。
(1)static_cast操作符的语法形式是static_cast(expression),当中。expression外面的圆括号不能省略,哪怕expression是一个简单的变量。
(2)通过static_cast仅仅能进行一些相关类型之间的“合理”转换。假设是类类型之间的转换,源类型和目标类型之间必须存在继承关系,否则会得到编译错误。
(3)static_cast所进行的是一种静态转换。是在编译时决定的。通过编译后,空间和时间效率实际上等价于C方式的强制类型转换。
(4)在C++中,仅仅想派生类对象的指针能够隐式转换为指向基类对象的指针。而要把指向积累对象的指针转换为指向派生类对象的指针,就须要借助static_cast操作符来完毕,其转换的风险是须要程序猿自己来承担。当然使用dynamic_cast更为安全。
(5)static_cast不能转换掉expression的const、volitale、或者__unaligned属性。
3.3 dynamic_cast
dynamic_cast是一个全然的动态操作符,仅仅能用于指针或者引用间的转换。并且dynamic_cast运算符所操作的指针或引用的对象必须拥有虚函数成员。否则出现编译错误。
原因是dynamic_cast牵扯到的面向对象的多态性,其作用就是在程序执行的过程中动态的检查指着或者引用指向的实际对象是什么以确定转换是否安全,而C++的类的多态性则依赖于类的虚函数。
详细的说。dynamic_cast能够进行例如以下的类型转换。
(1)在指向基类的指针(引用)与指向派生类的指针(引用)之间进行的转换。基类指针(引用)转换为派生类指针(引用)为向下转换。被编译器视为安全的类型转换。也能够使用static_cast进行转换。派生类指针(引用)转换为基类指针(引用)时, 为向上转换,被编译器视为不安全的类型转换,须要dynamic_cast进行动态的类型检測。
当然。static_cast也能够完毕转换,仅仅是存在不安全性。
(2)在多重继承的情况下。派生类的多个基类之间进行转换(称为交叉转换:crosscast)。
如父类A1指针实际上指向的是子类。则能够将A1转换为子类的还有一个父类A2指针。
3.3.1 dynamic_cast的向下转换
dynamic_cast在向下转换时(downcast),即将父类指针或者引用转换为子类指针或者引用时,会严格检查指针所指的对象的实际类型。
參见例如以下程序。
#include <iostream>
using namespace std;
class A{
public:
int i;
virtual void show(){
cout<<"class A"<<endl;
}
A(){int i=1;}
};
class B:public A{
public:
int j;
void show(){
cout<<"class B"<<endl;
}
B(){j=2;}
};
class C:public B{
public:
int k;
void show(){
cout<<"class C"<<endl;
}
C(){k=3;}
};
int main(int argc,char* argv[])
{
A* pa=NULL;
B b,*pb;
C *pc;
pa=&b;
pb=dynamic_cast<B*>(pa);
if(pb){
pb->show();
cout<<pb->j<<endl;
}
else
cout<<"Convertion failed"<<endl;
pc=dynamic_cast<C*>(pa);
if(pc){
pc->show();
cout<<pc->k<<endl;
}
else
cout<<"Convertion failed"<<endl;
getchar();
}
程序输出结果是:
class B
2
Convertion failed
由于指针pa所指的对象的实际类型是class B,所以将pa转换为B*类型没有问题,而将pa转换成C*类型时则失败。当指针转换失败时,返回NULL。
3.3.2 dynamic_cast的交叉转换
交叉转换(crosscast)是在两个“平行”的类对象之间进行。本来它们之间没有什么关系。将当中的一种转换为还有一种是不可行的。可是,假设类A和类B都是某个派生类C的基类,而指针所指的对象本身就是一个类C的对象,那么该对象既能够被视为类A的对象,也能够被视为类B的对象,类型A*(A&)和B*(B&)之间的转换就成为可能。见以下的样例。
#include <iostream>
using namespace std;
class A{
public:
int num;
A(){num=4;}
virtual void funcA(){}
};
class B{
public:
int num;
B(){num=5;}
virtual void funcB(){}
};
class C:public A,public B{};
int main(int argc,char* argv[])
{
C c;
A* pa;
B* pb;
pa=&c;
cout<<pa->num<<endl;
pb=dynamic_cast<B*>(pa);
cout<<"pa="<<pa<<endl;
if(pb)
{
cout<<"pb="<<pb<<endl;
cout<<"Conversion succeeded"<<endl;
cout<<pb->num<<endl;
}
else
cout<<"Conversion failed"<<endl;
getchar();
}
程序输出结果是:
能够看出,pa转换成pb之后,其值产生了变化。也就是说,在类C的对象中,类A的成员和类B的成员所占的位置(距离对象首地址的偏移量)是不同的。类B的成员要靠后一些。所以将A*转换为B*的时候。要对指针的位置进行调整。假设将在程序中的dynamic_cast替换成static_cast,则程序无法通过编译。由于这是编译器觉得类A和类B是两个“无关”的类。
3.4 reinterpret_cast
reinterpret_cast是一种最为“狂野”的转换。它在C++四中新的转换操作符中的能力是最强的,其转换能力与C的强制类型转换不分上下。正是由于其过于强大的转换能力,reinterpret_cast是C++语言中最不提倡使用的一种数据类型转换操作符,应该尽量避免使用。
主要用于转换一个指针为其它类型的指针,也同意将一个指针转换为整数类型,反之亦然。这个操作符能够在非相关的类型之间进行。只是其存在必有其价值。在一些特殊的场合,在确保安全性的情况下,能够适当使用。它一般用于函数指针的转换。见例如以下程序。
#include <iostream>
using namespace std;
typedef void (*pfunc)();
void func1()
{
cout<<"this is func1(),return void"<<endl;
}
int func2()
{
cout<<"this is func2(),return int"<<endl;
return 1;
}
int main(int argc,char* argv[])
{
pfunc FuncArray[2];
FuncArray[0]=func1;
FuncArray[1]=reinterpret_cast<pfunc>(func2);
for(int i=0;i<2;++i)
(*FuncArray[i])();
getchar();
}
程序输出结果:
this is func1(),return void
this is func2(),return int
由函数指针类型int(*)()转换为void(*)(),仅仅能通过reinterpret_cast进行,用其它的类型转换方式都会遭到编译器的拒绝。
并且从程序的意图来看,这里的转换是“合理”的。
只是,C++是一种强制类型安全的语言,即使是用interpret_cast,也不能随意地将某种类型转换为还有一种类型。C++编译器会设法保证“最低限度”的合理性。
语言内置的类型转换操作符无法胜任的工作须要程序猿手动重载相关转换操作符来完毕类型转换。
4. 手动重载相关类型转换操作符
在各种各样的类型转换中。用户自己定义的类类型与其它数据类型间的转换要引起注意。
这里要重点考察例如以下两种情况。
4.1不同类对象的相互转换
由一种类对象转换成还有一种类对象。
这样的转换无法自己主动进行,必须定义相关的转换函数。事实上这样的转换函数就是类的构造函数,或者将类类型作为类型转换操作符函数进行重载。此外。还能够利用构造函数完毕类对象的相互转换。见例如以下程序。
#include <iostream>
using namespace std;
class Student{
char name[20];
int age;
public:
Student(){};
Student(char *s, int a){
strcpy(name,s);
age=a;
}
friend class Team;
};
class Team{
int members;
Student monitor;
public:
Team(){};
Team(const Student& s):monitor(s),members(0){};
void Display()const{
cout<<"members' number :"<<members<<endl;
cout<<"monitor's name :"<<monitor.name<<endl;
cout<<"monitor's age :"<<monitor.age<<endl;
}
};
ostream& operator<<(ostream& out,const Team &t){
t.Display();
return out;
}
int main(int argc,char* argv[])
{
Student s("吕吕",23);
cout<<s;
getchar();
}
程序输出结果:
members’ number :0
monitor’s name :吕吕
monitor’s age :23
本来,输出操作符operator<<并不接受Student类对象作为參数。但由于可通过类Team的构造函数将Student类对象转换成Team类对象,所以输出操作能够成功进行。类的单參数构造函数实际上充当了类型转换函数。
4.2基本类型与类对象的相互转换
4.2.1基本类型转换为类对象
这样的转换仍能够借助于类的构造函数进行的。也就是说。在类的若干重载的构造函数中,有一些接受一个基本数据类型作为參数,这样就能够实现从基本数据类型到类对象的转换。
4.2.2类对象转换为基本数据类型
由于无法为基本数据类型定义构造函数,所以由对象想基本数据类型的转换必须借助于显示的转换函数。这些转换函数名由operator后跟基本数据类型名构成。以下是一个详细的样例。
#include <iostream>
using namespace std;
class A{
public:
operator int(){
return 1;
}
operator double(){
return 0.5;
}
};
int main(int argc,char* argv[])
{
A obj;
cout<<"Treating obj as an interger, its value is: "<<(int)obj<<endl;
cout<<"Treating obj as a double, its value is: "<<(double)obj<<endl;
getchar();
}
程序输出结果:
Treating obj as an interger, its value is: 1
Treating obj as a double, its value is: 0.5
在一个类中定义基本类型转换的函数,须要注意一下几点:
(1)类型转换函数仅仅能定义为一个类的成员函数。而不能定义为外部函数。类型转换函数与普通成员函数一样,也能够在类体中声明。在类外定义。
(2)类型转换函数一般是提供给类的客户使用的,所以应将訪问权限设置为public。否则无法被显示的调用。隐式的类型转换也无法完毕。
(3)类型转换函数既没有參数,也不显示的给出返回类型。
(4)转换函数必须有“return目的类型数据;”的语句,即必须返回目的类型数据最为函数的返回值。
(5)一个类能够定义多个类型转换函数。
C++编译器将依据目标数据类型选择合适的类型转换函数。在可能出现二义性的情况下,应显示地使用类型转换函数进行类型转换。
5.总结
(1)综上所述。数据类型转换相当于一次函数调用。调用的的结果是生成了一个新的数据实体。或者生成一个指向原数据实体但解释方式发生变化的指针(或引用)。
(2)编译器不给出不论什么警告也不报错的隐式转换总是安全的,否则必须使用显示的转换。必要时还要编写类型转换函数。
(3)使用显示的类型转换,程序猿必须对转换的安全性负责,这一点能够通过两种途径实现:一是利用C++语言提供的数据类型动态检查机制;而是利用程序的内在逻辑保证类型转换的安全性。
(4)dynamic_cast转换符仅仅能用于含有虚函数的类。
dynamic_cast用于类层次间的向上转换和向下转换,还能够用于类间的交叉转换。在类层次间进行向上转换。即子类转换为父类,此时完毕的功能和static_cast是同样的,由于编译器默认向上转换总是安全的。向下转换时,dynamic_cast具有类型检查的功能,更加安全。类间的交叉转换指的是子类的多个父类之间指针或引用的转换。
dynamic_cast能够实现执行时动态类型检查,依赖于对象的RTTI(Run-Time Type Information),通过虚函数表找到RTTI确定基类指针所指对象的真实类型,来确定是否能转换。
(5)interpre_cast相似于C的强制类型转换,多用于指针(和引用)类型间的转换。权利最大。也最危急。
static_cast全力较小,但大于dynamic_cast,用于普通的转换。进行类层次间的下行转换假设没有动态类型检查。是不安全的。
(6)const-cast仅仅用于去除指针或者引用类型的const和volatile属性。变量本身的属性不能被去除。
在进行类型转换时,请坚持例如以下原则:
(1)子类指针(或引用)转换为父类指针(或引用)编译器觉得总是是安全的,即向上转换。请使用static_cast,而非dynamic_cast,原因是static_cast效率高于dynamic_cast。
(2)父类指针(或引用)转换为子类指针(或引用)时存在风险,即向下转换,必须使用dynamic_cast进行动态类型检測。
(3)总领性原则:不要使用C风格的强制类型转换,而是使用标准C++中的四个类型转换符:static_cast、dynamic_cast、reinterpret_cast、和const_cast。
參考文献
[1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008.
C/C++数据类型的转换之终极无惑的更多相关文章
- printf()详解之终极无惑
1.printf()简介 printf()是C语言标准库函数,用于将格式化后的字符串输出到标准输出.标准输出,即标准输出文件,对应终端的屏幕.printf()申明于头文件stdio.h. 函数原型: ...
- sizeof,终极无惑(上)
0. 前向声明 sizeof,一个其貌不扬的家伙,引无数菜鸟竟折腰,小虾我当初也没少犯迷糊,秉着“辛苦我一个,幸福千万人”的伟大思想,我决定将其尽可能具体的总结一下. 但当我总结的时候才发现,这个问题 ...
- Java的基本数据类型与转换
1.1 Java为什么需要保留基本数据类型 http://www.importnew.com/11915.html 基本数据类型对大多数业务相关或网络应用程序没有太大的用处,这些应用一般是采用客户端/ ...
- java中的基本数据类型的转换
本文参考了如下两篇文章: https://my.oschina.net/joymufeng/blog/139952 http://www.cnblogs.com/lwbqqyumidi/p/37001 ...
- Java基础之数据类型和转换
一.常见的数据类型分类 1.java中基本数据类型分为三大类,即布尔类型,字符型,数值型.其中数值型又分为整型和浮点型.引用数据类型分为类,接口,数组,枚举,注解.具体如下: 注:一个字节 = 8位 ...
- javascript数据类型及转换
此篇数据类型和转换只限于ECMA规范,规范用了比较大的篇幅讲数据类型和类型转换,理解了这个最基本的概念对表达式.语句.执行环境.对象及继承都有非常大的帮助,遂整理如下: 数据类型和值 类型转换 表达式 ...
- python 基础-----数字,字符串,if while 循环 数据类型的转换简单介绍
一.第一个python小程序 首先我们要知道python创立的初衷是:Python崇尚优美.清晰.简单. 所以python比起其他的语言需要的工作量少了一半都不止,比如和现在一直霸占语言排行榜 榜首 ...
- C语言数据类型的转换
C语言的类型转换,一个是强制类型进行转换,而在这里要介绍的是自动的数据类型的转换,自动的数据类型转换很多时候是发生在多种数据类型混合使用的时候就会进行类型的转换,这样就会带来不能控制的结果,所以必须进 ...
- 27 JavaScript的引入&注释&弹窗&变量常量&数据类型及转换&内存&垃圾回收
JS的引入: 1 内部引入 绑定元素事件如onclick="" 绑定锚点如href="JavaScript:void(0)" script标签引入,注意:如果标 ...
随机推荐
- ANT公布SVN WEB项目到TOMCAT以及利用post-commit自己主动提交编译更新
开发者在本地提交更新到SVNserver后.往往须要測试人员又一次測试.为了将更新内容即时反映到測试server.能够利用post-commit脚本将SVN更新同步到測试server中. (1)利用S ...
- hdu2476String painter (区间DP)
Problem Description There are two strings A and B with equal length. Both strings are made up of low ...
- 写函数,输入n个数字输出最大值和最小值
# ,写函数,传入n个数,返回字典{‘max’:最大值,’min’:最小值}# 例如:min_max(2,5,7,8,4) 返回:{‘max’:8,’min’:2}(此题用到max(),min()内置 ...
- JVM源码分析之javaagent原理完全解读--转
原文地址:http://www.infoq.com/cn/articles/javaagent-illustrated 概述 本文重点讲述javaagent的具体实现,因为它面向的是我们Java程序员 ...
- node.js连接数据库登录注册,修改用户(页面的ajax请求)
首先给大家看一下目录结构,结构如下: index.html 首页(显示所有的用户信息) login.html 登录页 register.html 注册页 db.js 配置链接数据库参数 dbhelpe ...
- 「JavaSE 重新出发」05.03 反射
能够分析类能力的程序称为反射(reflection). 反射库(reflection library)提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵 Java 代码的程序. 反射机制可以用 ...
- POJ 3624 Charm Bracelet【01背包】
解题思路:直接套公式就能做的01背包, for(i=1;i<=n;i++) { for(v=w[i];v<=m;v++) f[i,v]=max(f[i,v],f[i-1,v-w[i]]+d ...
- win7(32位)旗舰版共享HP1010打印机给WINXP专业版
一.状况: 两台电脑,同一网段内,电脑A是WIN7系统(32位),电脑B是WINXP系统. ①电脑A(win7)已经安装完打印机驱动,能正常打印,对该打印机HP1010共享时,提示“无法保存打印机设置 ...
- Unity 指定区域随机实例化预制体Prefab 代码
using UnityEngine; public class NewBehaviourScript : MonoBehaviour { public GameObject prefab; void ...
- VS运行程序发生栈溢出的分析
这两天时间都浪费在解决各种栈溢出了,没想到最后各个部件合在一起的时候会出现这么多的问题,这其实是我第一次解决栈溢出问题,很认真的查了查资料,找了几种解决方法,但是作用都不大,拆了东墙补西墙,虽然知道及 ...