字符串与其余基本类型的转换——从C到C++11

转自 IBM 编译器中国开发团队的《C++11中的string - atoi/itoa》

在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时代,一般咱们遇到atoi(字符串到数值转换)的问题的时候咱们会使用<stdlib.h>中的atoi函数:编程

int num = atoi(cstr);api

这里的cstr一般为char或者const char类型的字符串。函数返回的结果则是该字符串所表示的一个十进制的integer。函数的整个效果则等同于<stdlib.h>中的另一个函数strtol:数组

int num = strtol(cstr, NULL, 10);安全

相比于atoi,strtol多了最后一个参数"radix"代表函数采用的是几进制(这个进制数能够从2到34,这个数值范围的缘由显而易见)。除去strtol会在出错时设置全局的errno外,其效果与atoi系列中的atol则几乎是彻底等同的。函数

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

int myint; char buf[SIZE]; sprintf(buf, "my data is %d", myint);学习

这里字符的输出控制交给了"%d"这样的特殊字符。经过特殊字符以及变长参数的配合(sprintf是变长参数函数),咱们得到预期的formatted I/O的输出。 这里咱们能够看到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++中这样的代码也并不是少见。

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

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

oss就是一个字符串流对象,能够用于itoa的工做。而

istringstream iss("12 14.1f"); int a; float b; iss >> a >> b; cout << a << " " << b << endl;

上面代码中的iss字符串流对象,则可用做atoi。 从设计上讲,std::stringstream算得上是一种好的设计。这是因为使用std::stringstream的代码看起来很是地直观。并且因为其来自于C++库,程序员一般也不太关心是否会有exception抛出--由于若是代码没有try-catch block的话,exception一旦抛出,程序就会直接直接终止(调用std::terminate)。这种解决出错的方式对于程序员来讲更为爽快,由于程序在问题点终止,就很容易找到出问题的代码位置。而C时代的atoi/itoa,如同咱们讲到的,须要程序员关注异常,若是漏过处理异常以后(其实这很常见),程序可能带病运行。固然,因为stringstream老是"附着"于一个内存能够自行管理的string对象,因此程序员一般也没必要担忧任何的内存分配问题。 从设计角度出发看,std::stringstream几乎无可挑剔。但在实际使用中,如咱们在上面提到的,不少人仍是愿意使用C中的处理方法来完成atoi/itoa。这大概有两方面的缘由:

  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++风格地、功能完备方式,但因为学习代价的增高以及格式化输出中的不便利性,其在实际场景中的应用也大大受限。

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

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

这里的to_string会根据参数的类型完成相应类型地转换。而:

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的程序而言也就足够了。

相关文章
相关标签/搜索