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

C++中的《数字字符串类型-数字类型转换总结》

2018-02-26 14:41 465 查看
C++11中,由于右值引用的引入,常为人诟病std::string的性能问题得到了很大的改善。另一方面,我们也可以看到新语言为std::string类增加了很多新的api。比较引人注意的有std::string的成员函数stoi系列,以及std::to_string全局函数。这两种API虽很不起眼,却为C++11的格式化输出(formatted I/O)增加了一种实用的手段。
我们可以依序会议一下C,C++98,C++11中我们是如何处理类似于atoi/itoa的问题的,或者说转换问题的。

一、C时代(头文件<stdlib.h>)

1. 字符串到数值转换:atoi函数

int num = atoi(cstr);
这里cstr通常为char*或者const char*类型的字符串。函数返回结果则是该字符串所表示的一个十进制的integer。

2. 等同效果,字符串到数值转换:strtol函数

int num = strtol(cstr, NULL, 10);
相比于atoi,strtol多了最后一个参数"radix"表明函数采用的是几进制(这个进制数可以从2到34,这个数值范围的原因显而易见)。除去strtol会在出错时设置全局的errno外,其效果与atoi系列中的atol则几乎是完全等同的。

3. 而C时代解决itoa(数值到字符串的转换)的时候,则采用了sprintf函数

int myint;
char buf[SIZE];
sprintf(buf, "my data is %d", myint);
这里字符的输出控制交给了"%d"这样的特殊字符。通过特殊字符以及变长参数的配合(sprintf是变长参数函数),我们获得预期的formatted I/O的输出。

4. 小结

这里我们可以看到C中对atoi/itoa的处理的特点,基本可以归纳如下:1. atoi不检查字符串中错误。这对使用API的程序员而言意味着他必须检查错误,或者必须判断出错误在实际使用中总是不存在或者是可以被程序忍受的。2. atoi的替代版本strtol检查字符串的错误,但使用的是POSIX中的标准方式,设置errno。这意味着使用strtol的程序员如果要检测字符串中的错误,需要在调用strtol后检测全局变量errno。3. sprintf不负责任何的内存管理。通常情况下,程序员都会被告诫使用snprintf或者其它有内存边界检查的版本替代sprintf。这样一来会减少发生缓冲区溢出的可能性。不过总的来说这只是一种编程中的防御手段,从程序员的角度而言,内存管理的烦恼依然存在。4. sprintf跟printf一样,不检查参数类型(因为是以变长函数的方式实现的),所以如果参数和escape character不匹配的话,会在运行时才发现不匹配的输出。不过相对于其它三点,这种错误是最容易修正的。所以说C中的atoi/itoa问题的解决方式并算不得让程序员愉悦。在坏的输入情况下,程序员必须小心处理各种异常,以防程序误入歧途。不过反过来看,C中的atoi/itoa的处理也非常直观,易于理解,所以即使在C++中这样的代码也并非少见。

5. 关于sprintf(参考:http://blog.csdn.net/u011317840/article/details/47808235

sprintf()跟printf()用法很相似,打印目的地不同。前者(sprintf)是打印到字符串中,后者(printf)直接在命令行上输出。 
sprintf() 是个变参函数,作用是格式化字符串。 
函数原型为: 
int sprintf( char* buffer, const char* format [, argument], … ); //format格式和printf()的格式控制符一样 
参数列表 
buffer:char型指针,指向将要写入的字符串的缓冲区。 
format:格式化字符串。 
[argument]…:可选参数,可以是任何类型的数据。 
返回值:字符串长度(strlen),打印到buffer中的字符数目 
功能: 
1. 将数字变量转换为字符串 
2. 得到整型变量的16进制和8进制字符串 
3. 连接多个字符串 
示例:
char str[256] = { 0 };
int data = 1024;
sprintf(str,"%d",data);    //将data转换为字符串,输出:1024
sprintf(str,"0x%X",data);  //获取data的十六进制,输出:0x400
sprintf(str,"0%o",data);   //获取data的八进制,输出:02000

const char *s1 = "Hello";
const char *s2 = "World";
sprintf(str,"%s %s",s1,s2);//连接字符串s1和s2,输出:Hello World

二、C++98时代(头文件<cstdlib>等)

到了C++98时代,atoi/itoa可以使用新的C++标准库<cstdlib>来完成。具体地就是使用C++的流(stream)模板类。值得注意的是,在C++98代码中,虽然字符串的存储使用字符串数组也是完全可以的,但在C++代码中使用std::string类型,内存可以自行有效地管理,而且成员函数可以抛出异常,所以更适用于C++代码。而关于std::string类型的流模板类型就是std::stringstream。通过全局重载的operator <<以及operator >>,std::stringstream可以很轻松地完成atoi或者是itoa的任务,比如:

1. oss就是一个字符串流对象,可以用于itoa(数值到字符串的转换)的工作。

ostringstream oss;
oss << 15 << " is int, " << 3.14f << " is float." << endl;
cout << oss.str();

2. iss字符串流对象,则可用作atoi(字符串到数值的转换)的工作。

istringstream iss("12 14.1f");
int a;
float b;
iss >> a >> b;
cout << a << " " << b << endl;   
从设计上讲,std::stringstream算得上是一种好的设计。这是由于使用std::stringstream的代码看起来非常地直观。而且由于其来自于C++库,程序员通常也不太关心是否会有exception抛出--因为如果代码没有try-catch block的话,exception一旦抛出,程序就会直接直接终止(调用std::terminate)。这种解决出错的方式对于程序员来说更为爽快,因为程序在问题点终止,就很容易找到出问题的代码位置。而C时代的atoi/itoa,如同我们讲到的,需要程序员关注异常,如果漏过处理异常之后(其实这很常见),程序可能带病运行。当然,由于stringstream总是"附着"于一个内存可以自行管理的string对象,所以程序员通常也不必担心任何的内存分配问题。从设计角度出发看,std::stringstream几乎无可挑剔。但在实际使用中,如我们在上面提到的,很多人还是愿意使用C中的处理方法来完成atoi/itoa。

3. 深入剖析。

1. std::stringstream在概念上的间接性。这点间接性来源于std::stringstream和std::string间的关联。通常情况下,一个std::stringstream对象总是会与其"附着"的std::string对象发生联系。或者其是从一个string对象(上例中的iss("12 14.1f"))构造而来以使用,或者其必须转化为一个string对象(上例中的oss.str())而使用。而新手常会会直觉地写出string a << 12 << " is int";这样的错误代码。2. 格式化输出的不便利性。相比于sprintf,std::stringstream是一个流对象,意味着其也有了更高的学习代价。简单的sprintf,只需要翻查escape character的手册,就能漂亮地进行格式化的输出。而使用流进行格式化输出的话,则需要控制一个状态机。很多时候,程序员需要关心上一状态对现有输出的影响。而且通常也意味着需要输入更多的代码。很多时候程序员都会觉得非常麻烦。所以即使sprintf在C++代码中缺失了类型匹配、异常处理、内存管理等等,程序员依然义无反顾地使用了它。(关于这一点,boost::format可能给出了一种跨平台的中间的解决方案)从以上两个方面看,使用std::stringstream完成atoi/itoa虽然是更为C++风格地、功能完备方式,但由于学习代价的增高以及格式化输出中的不便利性,其在实际场景中的应用也大大受限。

4. <sstream>:字符串数字转换的另一种方法

/*整型变字符串*/
int n = 10;
string str;
stringstream stream;

stream << n;
stream >> str;

cout<<str<<endl;
 stream.clear();//多次使用stringstream,要先清空下,不能使用stream.str("");否则下面输出10 

/*char* 变 string*/
char cStr[10] = "china";

stream << cStr;
stream >> str;

/*字符串 变 double*/
double n;
string str = "12.5";
stringstream stream;

stream << str;
stream >> n;

cout<<n<<endl;
 stream.clear();//多次使用stringstream,要先清空下,不能使用stream.str(""); 

/*字符串 变 char* */
string str1 = "china";
char cStr[10];

stream << str1;
stream >> cStr;

cout<<cStr<<endl;//输出china

 三、C++11时代(头文件<cstring>)

到了C++11中,标准委员会可能是注意到这种"简单比完备"更重要的情况,于是在C++11中,标准增加了全局函数std::to_string,以及std::stoi/stol/stoll等等函数。(最初的paper称之为simple numeric access,N1982)其用法非常简单:

 1. 数字转换为字符串,这里的to_string会根据参数的类型完成相应类型地转换。

string s;
s += to_string(12) + " is int, ";
s += to_string(3.14f) + " is float.";
cout << s << endl;

 2. 字符串转为数字,依然用stoi

string s("12");
int i = stoi(s);
cout << i << endl;
这样的代码则可以顺利完成atoi的任务。由于其是C++11引入的函数,所以具备C所不具备的所有的C++库代码特征:根据类型的处理,抛出异常,以及自动内存管理。 可以看到,std::to_string在实际使用中可能会涉及一些字符串的连结。如我们在文章一开始提到的,C++98中字符串连结一直是C++语言被诟病性能低于C的一个重要方面。而这在C++11引入了右值引用后得到了很大的缓解。因此此时std::to_string这样的函数的实用性就大大增强了。不过std::to_string并不是itoa的一种终极方式。以浮点数为例,to_string甚至连浮点数小数位显示控制这样基本的控制功能都不具备,因此其最大地特点还是突出在其易用性上。C++程序员不必定义一个std::stringstream对象就可以完成安全有效且不必关心任何内存的itoa工作。而std::stoi/stol/stoll...系列更是简单到只能完成一个数值的转换,比起总是返回std::stringstream &的operator >>比起来功能性就差很远了。后者能在一行代码中转化出多个数值。但前者最大地特点仍然突出在易用性上,不必"附着"一个std::stringstream类型。这对很多无需复杂atoi的程序而言也就足够了。
--------------------------------------itoa补充内容----------------------------------
function<stdlib.h>

itoa

char *  itoa ( int value, char * str, int base );
Convert integer to string (non-standard function)Converts an integer value to a null-terminated string using the specified base and stores the result in the array given by str parameter.

If base is 10 and value is negative, the resulting string is preceded with a minus sign (-). With any other base, value is always considered unsigned.

str should be an array long enough to contain any possible value: (sizeof(int)*8+1) for radix=2, i.e. 17 bytes in 16-bits platforms and 33 in 32-bits platforms.

Parameters

valueValue to be converted to a string.strArray in memory where to store the resulting null-terminated string.baseNumerical base used to represent the value as a string, between 2 and 36, where 10 means decimal base, 16hexadecimal, 8 octal, and 2 binary.

Return Value

A pointer to the resulting null-terminated string, same as parameter str.

Portability

This function is not defined in ANSI-C and is not part of C++, but is supported by some compilers.
这句话提出了重点原因,一些编译器是无法识别的.百度上也做了如下说明:itoa是广泛应用的非标准C语言和C++语言扩展函数。由于它不是标准C/C++语言函数,所以不能在所有的编译器中使用。但是,大多数的编译器(如Windows上的)通常在<stdlib.h>/<cstdlib>头文件中包含这个函数。
A standard-compliant alternative for some cases may be sprintf:
sprintf(str,"%d",value) converts to decimal base.
sprintf(str,"%x",value) converts to hexadecimal base.
sprintf(str,"%o",value) converts to octal base.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* itoa example */
#include <stdio.h>
#include <stdlib.h>

int main ()
{
int i;
char buffer [33];
printf ("Enter a number: ");
scanf ("%d",&i);
itoa (i,buffer,10);
printf ("decimal: %s\n",buffer);
itoa (i,buffer,16);
printf ("hexadecimal: %s\n",buffer);
itoa (i,buffer,2);
printf ("binary: %s\n",buffer);
return 0;
}
Edit & Run
Output:
Enter a number: 1750
decimal: 1750
hexadecimal: 6d6
binary: 11011010110
--------------------------------------------------------------------------------------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: