您的位置:首页 > 编程语言 > C语言/C++

C++数据类型转换详解

2015-01-07 20:50 716 查看
首先,C++中的数据类型转换分为三种:
1、自动转换是将两个数据类型的变量进行相互运算的过程中,系统把小的数据类型转化成大的数据类型后再进行运算,称之为运算过程中自动转换。
2,强制转换是指,在一个无论是什么类型的变量前加上你要转换成的数据类型的定义名,再将后面的变量用括号括起来,也叫加括号重新定义转换。
3,赋值转换,不改变数据类型,但也相当于转换,就是赋到什么类型,它的值就进入什么类型中去,它还是它,只是值有没有完整的赋过去,即有没有发生数据截断,即溢出。
自动转换
自动转换发生在不同数据类型的量混合运算时,由编译系统自动完成。自动转换遵循以下规则:

1.若参与运算量的类型不同,则先转换成同一类型,然后进行运算。

2.转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。

3.所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。

4.char型和short型参与运算时,必须先转换成int型。

5.在赋值运算中,赋值号两边量的数据类型不同时, 赋值号右边量的类型将转换为左边量的类型。 如果右边量的数据类型长度左边长时,将丢失一部分数据,这样会降低精度, 丢失的部分按四舍五入向前舍入。

当参加算术或比较运算的两个操作数类型不统一时,将简单类型向复杂类型转换,
char(short) -> int(long) -> float -> double

下面程序段表示了类型自动转换的规则。

void main(){
float PI=3.14159;
int s,r=5;
s=r*r*PI;
printf("s=%d\n",s);
} PI<--3.14159
s<--0,r<--5
s<--r*r*PI

显示程序运行结果:

float PI=3.14159;
int s,r=5;
s=r*r*PI;

本例程序中,PI为实型;s,r为整型。在执行s=r*r*PI语句时,r和PI都转换成double型计算,结果也为double型。但由于s为整型,故赋值结果仍为整型,舍去了小数部分。
强制类型转换
关于强制类型转换的问题,很多书都讨论过,写的最详细的是C++ 之父的《C++ 的设计和演化》。最好的解决方法就是不要使用C风格的强制类型转换,而是使用标准C++的类型转换符:static_cast, dynamic_cast。标准C++中有四个类型转换符:static_cast、dynamic_cast、reinterpret_cast、和const_cast。下面对它们一一进行介绍。
static_cast
用法:static_cast < type-id > ( expression_r_r )
该运算符把expression_r_r转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
把空指针转换成目标类型的空指针。
把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression_r_r的const、volitale、或者__unaligned属性。
dynamic_cast
用法:dynamic_cast < type-id > ( expression_r_r )
该运算符把expression_r_r转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;如果type-id是类指针类型,那么expression_r_r也必须是一个指针,如果type-id是一个引用,那么expression_r_r也必须是一个引用。
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
class B{
public:
int m_iNum;
virtual void foo();
};
class D:public B{
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>)中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。
另外,dynamic_cast还支持交叉转换(cross cast)。如下代码所示。
class A{
public:
int m_iNum;
virtual void f(){}
};

class B:public A{
};

class D:public A{
};

void foo(){
B *pb = new B;
pb->m_iNum = 100;
D *pd1 = static_cast<D *>(pb); //copile error
D *pd2 = dynamic_cast<D *>(pb); //pd2 is NULL
delete pb;
}
在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错;而使用 dynamic_cast的转换则是允许的,结果是空指针。
reinpreter_cast
用法:reinpreter_cast<type-id> (expression_r_r)
type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。
该运算符的用法比较多。
const_cast
用法:const_cast<type_id> (expression_r_r)
该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression_r_r的类型是一样的。
常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。
Voiatile和const类试。举如下一例:
class B{
public:
int m_iNum;
}
void foo(){
const B b1;
b1.m_iNum = 100; //comile error
B b2 = const_cast<B>(b1);
b2. m_iNum = 200; //fine
}
上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;使用const_cast把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1和b2是两个不同的对象。
最容易理解的解释:
dynamic_cast: 通常在基类和派生类之间转换时使用;
const_cast: 主要针对const和volatile的转换.
static_cast: 一般的转换,如果你不知道该用哪个,就用这个。
reinterpret_cast: 用于进行没有任何关联之间的转换,比如一个字符指针转换为一个整形数。

C++数据类型的转换方法分析:
从int到char*,或者反过来从char*到int,在C/C++中到底有多少种转换方法呢?符合标准的大概有四种。即C数据转换函数族、sprintf/snprintf/sscanf函数族、字符串流std::stringstream、std::strsteam。不符合标准却又广为使用的包括CString和boost::lexical_cast。本文只讨论符合标准的转换方法,其中std::strstream由于已经被C++标准委员为指定为不推荐使用的(deprecated),所以不予考虑了。下面重点讨论三种标准转换方法之间的优劣。源代码的地址是:http://download.csdn.net/source/631475

1. Int和char*或std::string之间的转换

C数据转换函数族

C数据转换函数族即包括itoa、atoi等数据类型转换函数在内的一大批C函数,在C语言时代曾经被大量使用。源代码如下:

int i = 10;

char szBuf[10] = "";

itoa(i, szBuf, 10);

cout<<"itoa: szBuf = "<<szBuf<<endl;

i = 0;

i = atoi(szBuf);

cout<<"atoi: i = "<<i<<endl;

使用还是比较简单的。一个最大的问题是:没有进行char*的越界检查,可能会造成数组溢出。

snprintf/sscanf

sprintf是用来格式化字符串的一个C函数,sscanf则是从字符串中读取值的一个C函数。由于Herb Sutter(Exceptional C++系列著作的作者)教导我们“永远也不要使用sprintf”,所以这里我们只使用snprintf。由于snprintf进入C标准较晚,所以在你的编译器中也许只能使用非标准的_snprintf(例如我的VC6平台)。源代码如下:

int i = 20;

char szBuf[10] = "";

memset(szBuf, 0, sizeof(szBuf));

_snprintf(szBuf, sizeof(szBuf), "%d", i);

cout<<"_snprintf: szBuf = "<<szBuf<<endl;

i = 0;

sscanf(szBuf,"%d",&i);

cout<<"sscanf: i = "<<i<<endl;

使用很简单,而且,似乎没有什么内存泄露或者数组越界。

std::stringstream

对流很熟悉的人可能会更快适应std::stringstream的解决方案:

#include <sstream>

using namespace std;

int i = 30;

string strRel;

ostringstream oss;

oss<<i;

strRel = oss.str();

cout<<"ostringstream: strRel = "<<strRel<<endl;

i = 0;

istringstream iss(strRel);

iss>>i;

cout<<"istringstream: i = "<<i<<endl;

使用较为复杂,而且,还使用了两个临时变量oss和iss,这必然带来性能上的开销。

2. double和char*或std::string之间的转换

1. C数据转换函数族

当开始进行double和char*之间的转换时,C数据转换函数的缺点暴露无疑。先看源代码:

double d = 3.1415926;

char szBuf[18] = "";

_gcvt(d, 9, szBuf);

cout<<"_gcvt: szBuf = "<<szBuf<<endl;

d = 0;

char* stopstring;

d = strtod(szBuf, &stopstring);

cout<<"strtod: d = "<<d<<endl;
首先转换函数的名字就让人大吃一惊,与itoa对应的不是我们想象的dtoa,而是_gcvt,而与atoi对应的是strtod。其次它们的参数很奇怪,没有msdn是不可能明白的。其次,数组越界依然存在。至此我想我们可以抛弃这组函数了。当然,更加无奈的理由在后面。

snprintf/sscanf

snprintf/sscanf表现不错,源代码如下:

double d = 3.1415926;

char szBuf[18] = "";

memset(szBuf, 0, sizeof(szBuf));

_snprintf(szBuf, sizeof(szBuf), "%f", d);

cout<<"sprintf: szBuf = "<<szBuf<<endl;

sscanf(szBuf, "%f", &d);

cout<<"sscanf: d = "<<d<<endl;
很好,很强大!

std::stringstream

std::stringstream的代码似乎没有任何改动,除了一个int类型改成了double类型:

double d = 9.1415926;

string strRel;

ostringstream oss;

oss<<d;

strRel = oss.str();

cout<<"ostringstream: strRel = "<<strRel<<endl;

d = 0;

istringstream iss(strRel);

iss>>d;

cout<<"istringstream: d = "<<d<<endl;
写到这里,我似乎看到模板函数在向我招手。

3. 复杂的转换

考虑一个经典的场景,从一个int,一个double和一个string中读出值,然后拼凑为一个输出的字符串。最后,从这个字符串中再将这几个值读出来。

C数据转换函数族

直接看代码:

int iAge = 25;

float fPayment = 3.25;

string strName ="Wang";

char szBuf[100] = "";

char szTemp[100];

strcpy(szBuf,"Age= ");

itoa(iAge, szTemp, 10);

strcat(szBuf, szTemp);

strcat(szBuf," ,Payment= ");

_gcvt(fPayment, 4, szTemp);

strcat(szBuf,szTemp);

strcat(szBuf," ,Name= ");

strcat(szBuf, strName.c_str());

cout<<"szBuf = "<<szBuf<<endl;
以上代码的表现真是惨不忍睹,费了几鼻子的劲好歹是转为目标字符串了。转换回来的代码也没有写。也许有,不过那个复杂程度,我看还是算了。而且,strcpy、strcat和几个转换函数都是危险的API,不检查越界的。

snprintf/sscanf

主要看看sscanf的表现:
int iAge = 25;

float fPayment = 3.25;

string strName ="Wang";

char szBuf[100] = "";

memset(szBuf, 0, sizeof(szBuf));

_snprintf(szBuf, sizeof(szBuf), "Age = %d, Payment = %f, Name = %s",iAge,fPayment,strName.c_str());

cout<<"sprintf: szBuf = "<<szBuf<<endl;

iAge = 0;

fPayment = 0.0;

memset(szTemp, 0, sizeof(szTemp));

sscanf(szBuf,"Age = %d, Payment = %f, Name = %s",&iAge,&fPayment,&szTemp);

strName = szTemp;

cout<<"sscanf: Age = "<<iAge<<",Payment="<<fPayment<<",name="<<strName<<endl;
snprintf表现还是一如既往的强大。sscanf的表现简直就是perfect,但是要注意,sscanf的样式字符串一定要和snprintf中的样式字符串一模一样,否则其后果是不可预计的。例如,我稍微改动了几个字符,最后的strname就读取错误了。

std::stringstream

std::stringstream的代码很长很长:

int iAge = 25;

float fPayment = 3.25;

string strName ="Wang";

string strRel;

ostringstream oss;

oss<<"Age = "<<iAge<<", Payment = "<<fPayment<<", Name = "<<strName;

strRel = oss.str();

cout<<"ostringstream: strRel = "<<strRel<<endl;

iAge = 0;

fPayment = 0.0;

strName = "";

istringstream iss(strRel);

string strTemp;

iss>>strTemp>>strTemp>>iAge>>strTemp>>strTemp>>strTemp>>fPayment

>>strTemp>>strTemp>>strTemp>>strName;

cout<<"istringstream: Age = "<<iAge<<",Payment="<<fPayment<<",name="<<strName<<endl;
ostringstream的表现还是不错的,比snprintf毫不逊色,甚至更好一点,因为它不用记样式符。但是看到istringstream的这几行代码,估计大部分人要吐血了:

string strTemp;

iss>>strTemp>>strTemp>>iAge>>strTemp>>strTemp>>strTemp>>fPayment

>>strTemp>>strTemp>>strTemp>>strName;
为什么中间有那么多strTemp?因为每当istringstream每当遇到由一个空格或者非数字字符包围的字符串时就必须输入到一个string中,因此例如“,”或者“=”都必须占用一个string来输入。总之,当字符串很复杂时,很麻烦。

4. 小结

从易用性、安全性和效率三个方面来考察以上方法。

易用性按从好到坏排列依次是:snprintf/sscanf、std::stringstream、C数据转换函数族。

安全性按从好到坏排列依次是:std::stringstream、snprintf/sscnaf、C数据转换函数族。

效率按从好到坏排列依次是:snprintf/sscanf、C数据转换函数族、std::stringstream。

到此我们可以抛弃C数据转换函数族了,接下来从其他方面来考察剩下的方法。

5. 模板函数

考虑写一个从任何内置数据类型到字符串的转换函数。此时只能使用std::stringstream了。

template<typename T> string to_str(T val)

{

ostringstream oss;

oss<<val;

return oss.str();

}

string strFromInt = to_str(10);

cout<<strFromInt<<endl;

string strFromDouble = to_str(3.1415926);

cout<<strFromDouble<<endl;
换句话说,也就是std::stringstream是类型安全的,而snprintf不是。

6. 精度和格式

前面的例子都忽略了精度,事实上要进行精度和格式的设置,snprintf和std::stringstream的学习时间是差不多的。这里就不一一叙述了。

7. 再谈安全性

snprintf是安全的,因为它会检查数组越界并阻止这种行为。但是当目标字符数组长度小于需求时,其结果是不正确的。sscanf也是同样,当它的样式字符串不匹配时,其结果是未知的。

std::stringstream是更安全的。由于它采用了内存自动管理机制。无论在任何时候,只要它没有抛出异常,其结果总是正确的。

8. 总结

在易用性和效率方面,snprintf/sscanf都比std::stringstream强,因此只要程序员能够保证目标字符数组长度够用时,都可以放心使用snprintf。sscanf也是同理,只要程序员保证其样式字符串正确,其结果也总是正确的。

而std::stringstream的最大优点是具有模板亲和力,可以用于泛型编程。在效率不是很重要的情况下,建议使用std::stringstream。若你是一位非常谨慎的程序员,建议你总是使用std::stringstream,因为它是最安全的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: