C++11中的string - atoi/itoa

2023-12-13 09:58
文章标签 c++ string itoa atoi

本文主要是介绍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);

这里的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的任务,比如:

 

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

 


这篇关于C++11中的string - atoi/itoa的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/488026

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给

2024/9/8 c++ smart

1.通过自己编写的class来实现unique_ptr指针的功能 #include <iostream> using namespace std; template<class T> class unique_ptr { public:         //无参构造函数         unique_ptr();         //有参构造函数         unique_ptr(