从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; |
使用还是比较简单的。一个最大的问题是:itoa没有进行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];
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;
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,因为它是最安全的。
转自:http://www.uml.org.cn/c++/200908315.asp