如何使用C ++将一个字符串(以char *表示)解析为整数?强大且清晰的错误处理是一个优点(而不是返回零)。
如何使用C ++将一个字符串(以char *表示)解析为整数?强大且清晰的错误处理是一个优点(而不是返回零)。
这是我的第一条建议:不要使用stringstream。虽然一开始可能看起来很简单,但如果你想要稳健的处理和良好的错误处理,你会发现需要做很多额外的工作。
下面是一个直觉上似乎应该起作用的方法:
bool str2int (int &i, char const *s)
{
std::stringstream ss(s);
ss >> i;
if (ss.fail()) {
// not an integer
return false;
}
return true;
}
这里存在一个重大问题:str2int(i, "1337h4x0r")
会愉快地返回true
,而i
的值将变为1337
。我们可以通过确保在转换后没有更多字符留在stringstream
中来解决这个问题:bool str2int (int &i, char const *s)
{
char c;
std::stringstream ss(s);
ss >> i;
if (ss.fail() || ss.get(c)) {
// not an integer
return false;
}
return true;
}
我们解决了一个问题,但还有其他几个问题。
如果字符串中的数字不是以10为基底怎么办?我们可以尝试通过将流设置到正确的模式(例如ss << std::hex
)来适应其他基数,然而这意味着调用者必须事先知道数字的基数 - 但调用者怎么可能知道呢?调用者甚至还不知道它是一个数字!他们怎么能知道它的基数呢?我们可以强制所有输入到程序的数字都必须是十进制,并拒绝十六进制或八进制输入,因为这不够灵活或健壮。这个问题没有简单的解决方案。你不能仅仅为每个基数尝试一次转换,因为十进制转换总是会成功地将八进制数字(带前导零)转换为十进制,而八进制转换可能会对某些十进制数字成功。所以现在你必须检查是否有前导零。但等等!十六进制数字也可以以前导零开头(0x...)。叹息。
即使您成功处理了以上问题,仍然存在另一个更大的问题:调用者需要区分坏的输入(例如“123foo”)和超出int
范围的数字(例如32位int
的“4000000000”)怎么办?使用stringstream
,我们无法进行此区分。我们只知道转换成功或失败。如果它失败了,我们就没有办法知道为什么失败。正如您所见,如果您想要强大的和清晰的错误处理,stringstream
并不是一个理想的选择。
这让我想到我的第二个建议:不要使用Boost的lexical_cast
。考虑一下lexical_cast
文档所说的:
在需要更高程度控制转换时,std::stringstream 和 std::wstringstream 提供了一条更适当的路径。在需要非流式转换时,lexical_cast 不是解决该场景的正确工具。
什么?我们已经看到stringstream
的控制级别很低,然而它却说如果需要“更高的控制级别”,应该使用stringstream
而不是lexical_cast
。此外,因为lexical_cast
只是对stringstream
的包装,所以它遭受与stringstream
相同的问题:对多个数字基数的支持差和差劲的错误处理。
幸运的是,有人已经解决了所有以上问题。C标准库包含strtol
和其它函数,它们没有这些问题。
enum STR2INT_ERROR { SUCCESS, OVERFLOW, UNDERFLOW, INCONVERTIBLE };
STR2INT_ERROR str2int (int &i, char const *s, int base = 0)
{
char *end;
long l;
errno = 0;
l = strtol(s, &end, base);
if ((errno == ERANGE && l == LONG_MAX) || l > INT_MAX) {
return OVERFLOW;
}
if ((errno == ERANGE && l == LONG_MIN) || l < INT_MIN) {
return UNDERFLOW;
}
if (*s == '\0' || *end != '\0') {
return INCONVERTIBLE;
}
i = l;
return SUCCESS;
}
这个函数处理所有错误情况,并支持从2到36的任何进制转换,非常简单。如果base
为零(默认值),它将尝试从任何基数进行转换。或者,调用者可以提供第三个参数并指定仅尝试特定基数的转换。它是强大而健壮的,能够以极小的努力处理所有错误。
使用strtol
(等)的其他原因:
没有任何好理由使用其他方法。
strtol
是线程安全的。POSIX还要求errno
使用线程本地存储。即使在非POSIX系统上,几乎所有在多线程系统中实现的errno
都使用线程本地存储。最新的C++标准要求errno
符合POSIX规范。最新的C标准也要求errno
具有线程本地存储。即使在绝对不符合POSIX的Windows上,errno
也是线程安全的,因此strtol
也是线程安全的。 - Dan Mouldingstd::stol
,它会适当地抛出异常而不是返回常量值。请使用它来代替。 - fuzzyTewstd::stol
之前,我写了这个答案。尽管如此,我认为说这是“在C++中使用C编码”是不公平的。当std::strtol
被明确地纳入C++语言时,将其称为C编码是愚蠢的。我的答案在撰写时完全适用于C++,即使有了新的std::stol
,它仍然适用。在每种编程情况下,调用可能引发异常的函数并不总是最好的选择。 - Dan MouldingC++11引入了一些函数来完成这个任务:stoi,stol,stoll,stoul等等。
int myNr = std::stoi(myString);
在转换错误时它会抛出异常。
即使这些新函数仍然存在与Dan指出的相同问题:它们会将字符串"11x"愉快地转换为整数"11"。
更多信息请参见:http://en.cppreference.com/w/cpp/string/basic_string/stol
size_t
不等于字符串的长度,则它会提前停止。在这种情况下,它仍将返回 11,但 pos
将为 2 而不是字符串长度 3。https://coliru.stacked-crooked.com/a/cabe25d64d2ffa29 - Zoe stands with Ukraine这是比atoi()更为安全的C语言方式
const char* str = "123";
int i;
if(sscanf(str, "%d", &i) == EOF )
{
/* error */
}
C++标准库中的stringstream:(感谢CMS)
int str2int (const string &str) {
stringstream ss(str);
int num;
if((ss >> num).fail())
{
//ERROR
}
return num;
}
#include <boost/lexical_cast.hpp>
#include <string>
try
{
std::string str = "123";
int number = boost::lexical_cast< int >( str );
}
catch( const boost::bad_lexical_cast & )
{
// Error
}
编辑:根据CMS和jk在原帖中的评论,修复了stringstream版本以处理错误。
老牌的 C 语言方法仍然有效。我建议使用 strtol 或 strtoul 函数。你可以通过返回状态和 'endPtr' 来输出良好的诊断信息。它还可以很好地处理多个进制。
std::strtol
的情况下,你没有办法知道你是否成功解析了一个0或者函数是否失败,除非你手动检查字符串是否解析为0,而当你这样做时,你会不必要地重复工作。更现代化的方法(std::from_chars
)不仅告诉你函数何时失败,而且还告诉你为什么失败,这有助于向最终用户提供反馈。 - Pharap从C++17开始,您可以使用位于<charconv>
头文件中的std::from_chars
,如此处所述。
例如:
#include <iostream>
#include <charconv>
#include <array>
int main()
{
char const * str = "42";
int value = 0;
std::from_chars_result result = std::from_chars(std::begin(str), std::end(str), value);
if(result.error == std::errc::invalid_argument)
{
std::cout << "Error, invalid format";
}
else if(result.error == std::errc::result_out_of_range)
{
std::cout << "Error, value too big for int range";
}
else
{
std::cout << "Success: " << result;
}
}
你可以使用C++标准库中的stringstream:
stringstream ss(str);
int x;
ss >> x;
if(ss) { // <-- error handling
// use x
} else {
// not a number
}
如果在尝试读取整数时遇到非数字字符,流状态将被设置为失败。
有关C ++中错误处理和流的陷阱,请参见Stream pitfalls.
您可以使用stringstream的
int str2int (const string &str) {
stringstream ss(str);
int num;
ss >> num;
return num;
}
我认为以下这三个链接总结得很好:
stringstream和lexical_cast的解决方案基本相同,因为lexical_cast使用了stringstream。
lexical_cast的一些特殊化采用不同的方法,请参见http://www.boost.org/doc/libs/release/boost/lexical_cast.hpp以获取详细信息。整数和浮点数现在已经专门针对整数转换成字符串进行了特殊化处理。
可以将lexical_cast特殊化为自己的需求并使其快速。这将是满足所有人、清晰简单的最终解决方案。
上述文章已经比较了不同的整数<->字符串转换方法。以下方法是有意义的:旧的c方式、spirit.karma、fastformat、简单的naive循环。
对于整数转换成字符串,lexical_cast是可以接受的。
使用lexical_cast将字符串转换为整数不是一个好主意,因为它比atoi慢10-40倍,这取决于所使用的平台/编译器。
Boost.Spirit.Karma似乎是最快的将整数转换成字符串的库。
ex.: generate(ptr_char, int_, integer_number);
从上面提到的文章中,最基本简单的循环方法是将字符串转换为整数的最快方法,但显然不是最安全的方法,strtol()似乎是一种更安全的解决方案。
int naive_char_2_int(const char *p) {
int x = 0;
bool neg = false;
if (*p == '-') {
neg = true;
++p;
}
while (*p >= '0' && *p <= '9') {
x = (x*10) + (*p - '0');
++p;
}
if (neg) {
x = -x;
}
return x;
}
C++字符串工具包(StrTk)提供以下解决方案:
static const std::size_t digit_table_symbol_count = 256;
static const unsigned char digit_table[digit_table_symbol_count] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xFF - 0x07
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x08 - 0x0F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x10 - 0x17
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x18 - 0x1F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x20 - 0x27
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x28 - 0x2F
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 0x30 - 0x37
0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x38 - 0x3F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x40 - 0x47
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x48 - 0x4F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x50 - 0x57
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x58 - 0x5F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x60 - 0x67
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x68 - 0x6F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x70 - 0x77
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x78 - 0x7F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x80 - 0x87
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x88 - 0x8F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x90 - 0x97
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x98 - 0x9F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xA0 - 0xA7
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xA8 - 0xAF
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xB0 - 0xB7
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xB8 - 0xBF
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xC0 - 0xC7
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xC8 - 0xCF
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xD0 - 0xD7
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xD8 - 0xDF
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xE0 - 0xE7
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xE8 - 0xEF
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xF0 - 0xF7
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 0xF8 - 0xFF
};
template<typename InputIterator, typename T>
inline bool string_to_signed_type_converter_impl_itr(InputIterator begin, InputIterator end, T& v)
{
if (0 == std::distance(begin,end))
return false;
v = 0;
InputIterator it = begin;
bool negative = false;
if ('+' == *it)
++it;
else if ('-' == *it)
{
++it;
negative = true;
}
if (end == it)
return false;
while(end != it)
{
const T digit = static_cast<T>(digit_table[static_cast<unsigned int>(*it++)]);
if (0xFF == digit)
return false;
v = (10 * v) + digit;
}
if (negative)
v *= -1;
return true;
}
InputIterator可以是无符号char*、char*或std::string 迭代器,T应该是带符号整数,比如signed int、int或long。
v = (10 * v) + digit;
会不必要地溢出,特别是当文本值为 INT_MIN
时。相对于简单的 digit >= '0' && digit <= '9'
,表格的价值存疑。 - chux - Reinstate Monica