WCHAT:即wchar_t
LPSTR:即 char *,指向以'\0'结尾的8位(单字节)ANSI字符数组指针
LPWSTR:即wchar_t *,指向'\0'结尾的16位(双字节)Unicode字符数组指针
LPCSTR:即const char *
LPCWSTR:即const wchar_t *
LPCVOID:即const void *
DWORD:即unsigned long 4字节
WORD:即unsigned short 2字节
BYTE:即unsigned char 1字节
LPDWORD:即unsigned long *
LPLONG:即long *
LPINT:即int *
用标准c++进行string与各种内置数据类型的转换
要实现这个目标,非 stringstream 类莫属。这个类在<sstream>头文件中定义, <sstream>库定义了三种类:istringstream、ostringstream和stringstream,分别用来进行流的输入、输出和输入输出操作。另外,每个类都有一个对应的宽字符集版本。简单起见,我主要以stringstream为中心,因为每个转换都要涉及到输入和输出操作。示例1示范怎样使用一个stringstream对象进行从
string到int类型的转换
注意,<sstream>使用string对象来代替字符数组。这样可以避免缓冲区溢出的危险。而且,传入参数和目标对象的类型被自动推导出来,即使使用了不正确的格式化符也没有危险。
示例1:
std::stringstream stream;
string result="10000";
int n = 0;
stream << result;
stream >> n;//n等于10000
int到string类型的转换
string result;
int n = 12345;
stream << n;
result =stream.str();// result等于"12345"
重复利用stringstream对象
如果你打算在多次转换中使用同一个stringstream对象,记住再每次转换前要使用clear()方法,在多次转换中重复使用同一个stringstream(而不是每次都创建一个新的对象)对象最大的好处在于效率。stringstream对象的构造和析构函数通常是非常耗费CPU时间的。经试验,单单使用clear()并不能清除stringstream对象的内容,仅仅是了该对象的状态,要重复使用同一个stringstream对象,需要使用str()重新初始化该对象。
示例2:
std::stringstream strsql;
for (int i= 1; i < 10; ++i)
{
strsql << "insert into test_tab values(";
strsql << i << ","<< (i+10) << ");";
std::string str = strsql.str(); // 得到string
res = sqlite3_exec(pDB,str.c_str(),0,0, &errMsg);
std::cout << strsql.str() << std::endl;
strsql.clear();
strsql.str("");
}
转换中使用模板
也可以轻松地定义函数模板来将一个任意的类型转换到特定的目标类型。例如,需要将各种数字值,如int、long、double等等转换成字符串,要使用以一个string类型和一个任意值 t 为参数的to_string()函数。to_string()函数将 t 转换为字符串并写入result中。使用str()成员函数来获取流内部缓冲的一份拷贝:
示例3:
template<class T>
void to_string(string & result,const T& t)
{
ostringstream oss;//创建一个流
oss<<t;//把值传递如流中
result=oss.str();//获取转换后的字符转并将其写入result
}
这样,你就和衣轻松地将多种数值转换成字符串了:
to_string(s1,10.5);//double到string
to_string(s2,123);//int到string
to_string(s3,true);//bool到string
可以更进一步定义一个通用的转换模板,用于任意类型之间的转换。函数模板convert()含有两个模板参数out_type和in_value,功能是将in_value值转换成out_type类型:
template<class out_type,class in_value>
out_type convert(const in_value & t)
{
stringstream stream;
stream<<t;//向流中传值
out_type result;//这里存储转换结果
stream>>result;//向result中写入值
return result;
}
这样使用convert():
double d;
string salary;
string s=”12.56”;
d=convert<double>(s);//d等于12.56
salary=convert<string>(9000.0);//salary等于”9000”
结论
在过去留下来的程序代码和纯粹的C程序中,传统的<stdio.h>形式的转换伴随了我们很长的一段时间。但是,如文中所述,基于stringstream的转换拥有类型安全和不会溢出这样抢眼的特性,使我们有充足得理由抛弃<stdio.h>而使用<sstream>。
从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,因为它是最安全的。