如何在C ++中将字符串解析为int?


260

将字符串(以char *给定)解析为int的C ++方法是什么?强大而清晰的错误处理是一个加号(而不是返回零)。


21
下面的一些示例如何处理:codeproject.com/KB/recipes/Tokenizer.aspx它们非常有效并且有点优雅

@Beh Tou Cheh,如果您认为这是解析int的好方法,请将其发布为答案。
尤金·横田

Answers:


164

在新的C ++ 11中,有相应的函数:stoi,stol,stoll,stoul等。

int myNr = std::stoi(myString);

它将在转换错误时引发异常。

即使这些新函数仍然具有Dan指出的相同问题:它们将愉快地将字符串“ 11x”转换为整数“ 11”。

查看更多:http : //en.cppreference.com/w/cpp/string/basic_string/stol


4
但是他们接受的参数要多得多,其中一个是指向size_t的点,如果不为null,则将其设置为第一个未转换的字符
Zharf 2012年

是的,使用std :: stoi的第二个参数可以检测到无效输入。但是,您仍然必须滚动自己的转换功能...
CC。

就像接受的答案一样,但是有了这些标准功能,它会更加干净,imo
Zharf 2012年

204

不该做什么

这是我的第一条建议:不要为此使用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")会很高兴地返回truei获得值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在尝试进行转换之前,我们可以通过将流设置为正确的模式(例如)来尝试容纳其他基础。但这意味着呼叫者必须先验知道数字的基础-呼叫者怎么可能知道呢?呼叫者尚不知道电话号码是多少。他们甚至不知道那一个号码!怎么期望他们知道这是什么基础?我们可以要求所有输入到程序中的数字必须以10为底,并拒绝将十六进制或八进制输入视为无效。但这不是非常灵活或健壮。没有解决此问题的简单方法。您不能简单地为每个基数尝试一次转换,因为十进制转换对于八进制数(带有前导零)将始终成功,而八进制转换对于某些十进​​制数可能会成功。因此,现在您必须检查前导零。可是等等!十六进制数也可以以前导零(0x ...)开头。叹。

即使你在处理上述问题取得成功,还有另一个更大的问题:如果主叫方需要坏输入(如“123foo”)和一个数字,范围的出区分int(例如,“40亿”为32位int)?使用stringstream,无法进行这种区分。我们只知道转换是成功还是失败。如果失败,我们将无法知道失败的原因。如您所见,stringstream如果您需要鲁棒性和清晰的错误处理,则还有很多不足之处。

这引出了我的第二条建议:请勿lexical_cast为此使用Boost的。考虑一下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(和家人)的其他原因:

  • 它表现出更好的运行时性能
  • 它引入了较少的编译时开销(其他的从头文件中获取的SLOC则增加了近20倍)
  • 结果是最小的代码大小

绝对没有充分的理由使用任何其他方法。


22
@JamesDunne:POSIX必须strtol是线程安全的。POSIX还要求errno使用线程本地存储。即使在非POSIX系统上,几乎所有errno在多线程系统上的实现都使用线程本地存储。最新的C ++标准要求errno符合POSIX。最新的C标准还要求errno具有线程本地存储。即使在绝对不符合POSIX的Windows上,errno也是线程安全的,并且通过扩展也是如此strtol
Dan Moulding

7
对于使用boost :: lexical_cast的原因,我实在无法理解。正如他们所说,std :: stringstream确实提供了很多控制-从错误检查到确定自己的基础,您可以做所有的事情。当前的文档是这样写的:“对于更多涉及的转换,例如比lexical_cast的默认行为所提供的更精确的控制或格式需要更严格的控制,建议使用常规的std :: stringstream方法。”
2012年

8
这在C ++中是不合适的C编码。std::stol为此,标准库包含该库,它将适当地引发异常,而不是返回常量。
FuzzyTew

22
@fuzzyTew我std::stol甚至在将其添加到C ++语言之前就写了这个答案。就是说,我不认为这是“ C ++中的C编码”是不公平的。std::strtol当它明确地属于C ++语言的一部分时,可以说这是C编码。我的回答在编写C ++时完美地适用于C ++,即使是新版本,它仍然适用std::stol。在每种编程情况下,调用可能引发异常的函数并不总是最好的。
Dan Moulding

9
@fuzzyTew:磁盘空间不足是一种特殊情况。计算机生成的格式错误的数据文件是一种例外情况。但是用户输入中的错别字也不例外。拥有一种能够处理正常的,非异常的解析失败的解析方法是很好的。
Ben Voigt 2015年

67

这是比atoi()更安全的C方法

const char* str = "123";
int i;

if(sscanf(str, "%d", &i)  == EOF )
{
   /* error */
}

具有标准库stringstream的 C ++ :(感谢CMS

int str2int (const string &str) {
  stringstream ss(str);
  int num;
  if((ss >> num).fail())
  { 
      //ERROR 
  }
  return num;
}

使用Boost库:(感谢jk

#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
}

编辑:修复了stringstream版本,以便其处理错误。(感谢CMS和jk对原始帖子的评论)


1
请更新您的stringstream版本,以包括对stringstream :: fail()的检查(按发问人的“稳健和清除错误处理”的要求)
jk。

2
您的stringstream版本将接受“ 10haha”之类的内容而不会有所抱怨
Johannes Schaub-litb

3
将其更改为从((SS >> NUM).fail())如果你想同样的处理类似的lexical_cast((SS >> NUM).fail()&&(SS >> WS).EOF()!)
约翰内斯Schaub-litb

3
具有标准库stringstream方法的C ++即使使用.fail()检查,也不适用于诸如“ 12-SomeString”之类的字符串。
captonssj

C ++ 11现在为此提供了标准的快速功能
FuzzyTew

21

好的“旧C”方式仍然有效。我建议使用strtol或strtoul。在返回状态和“ endPtr”之间,您可以提供良好的诊断输出。它还可以很好地处理多个基地。


4
哦,在编程C ++时请不要使用这些古老的C语言。在C ++中,有更好/更容易/更干净/更现代/更安全的方法来做到这一点!
jk。

27
当人们关注“更现代”的解决问题的方式时,这很有趣。
米勒

@Jason,相比C的IMO更强的类型安全和错误处理是更现代的理念
尤金·横田

6
我看了其他答案,到目前为止,显然没有什么比这更好/更容易/更清洁或更安全了。海报说他有一个字符*。这限制了您将获得的安全性:)
克里斯·阿尔金


16

您可以使用来自C ++标准库libraray的字符串流:

stringstream ss(str);
int x;
ss >> x;

if(ss) { // <-- error handling
  // use x
} else {
  // not a number
}

如果在尝试读取整数时遇到非数字,则流状态将设置为失败。

有关C ++中错误处理和流的陷阱,请参阅流陷阱


2
即使使用“ stream state”检查,C ++ stringstream方法也不适用于诸如“ 12-SomeString”之类的字符串。
captonssj

10

您可以使用stringstream的

int str2int (const string &str) {
  stringstream ss(str);
  int num;
  ss >> num;
  return num;
}

4
但这不会处理任何错误。您必须检查流中是否有故障。
jk。

1
正确,您必须检查流if((ss >> num).fail()){// ERROR}
CMS

2
C ++ stringstream方法即使使用“ stream state”检查,也不适用于诸如“ 12-SomeString”之类的字符串
captonssj

8

我认为这三个链接可以概括为:

stringstream和lexical_cast解决方案与使用stringstream的词法转换大致相同。

词汇转换的某些专业化使用不同的方法,有关详细信息,请参见http://www.boost.org/doc/libs/release/boost/lexical_cast.hpp。整数和浮点数现在专门用于整数到字符串的转换。

可以根据自己的需要来专门研究lexical_cast,并使其快速发展。这将是使各方满意的最终解决方案,既干净又简单。

已经提到的文章显示了转换整数<->字符串的不同方法之间的比较。遵循以下方法是有意义的:旧的c-way,spirit.karma,fastformat,简单的幼稚循环。

Lexical_cast在某些情况下还可以,例如,从int到字符串的转换。

使用词法转换将字符串转换为int并不是一个好主意,因为它比atoi慢10-40倍,具体取决于所使用的平台/编译器。

Boost.Spirit.Karma似乎是将整数转换为字符串的最快的库。

ex.: generate(ptr_char, int_, integer_number);

而且上面提到的文章中的基本简单循环是将字符串转换为int的最快方法,显然不是最安全的方法,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;
}

7

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应该是一个带符号的int,例如带符号的int,int或long


1
警告此实现看起来不错,但据我所知,它不能处理溢出。
文尼·法尔科

2
代码不处理溢出。 v = (10 * v) + digit;文本值为的字符串输入不必要地溢出INT_MIN。表格的价值与简单相比值得怀疑digit >= '0' && digit <= '9'
chux-恢复莫妮卡

6

如果你有C ++ 11,适当的解决方案,现在是C ++整数转换函数<string>stoistolstoulstollstoull。当输入不正确时,它们会抛出适当的异常,并在后台使用快速和小型strto*功能。

如果您坚持使用C ++的早期版本,那么在您的实现中模拟这些功能将是您的向前移植。


6

从C ++ 17开始,您可以按照此处记录std::from_chars<charconv>标题使用。

例如:

#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;
    }
}

另外,它还可以处理其他基数,例如十六进制。


3

我喜欢Dan Moulding的答案,我将添加一些C ++样式:

#include <cstdlib>
#include <cerrno>
#include <climits>
#include <stdexcept>

int to_int(const std::string &s, int base = 0)
{
    char *end;
    errno = 0;
    long result = std::strtol(s.c_str(), &end, base);
    if (errno == ERANGE || result > INT_MAX || result < INT_MIN)
        throw std::out_of_range("toint: string is out of range");
    if (s.length() == 0 || *end != '\0')
        throw std::invalid_argument("toint: invalid string");
    return result;
}

通过隐式转换,它对std :: string和const char *均适用。这对于基本转换也很有用,例如all to_int("0x7b")to_int("0173")and to_int("01111011", 2)to_int("0000007B", 16)and to_int("11120", 3)to_int("3L", 34);将返回123。

std::stoi与之不同的是,它在C ++ 11之前的版本中有效。也不像std::stoiboost::lexical_caststringstream它引发了怪异的字符串,如“123hohoho”例外。

注意:此函数可以容忍前导空格,但不能容忍尾随空格,即to_int(" 123")to_int("123 ")抛出异常时返回123 。确保这对于您的用例是可接受的,或者调整代码。

此类功能可能是STL的一部分...


2

我知道将String转换为int的三种方法:

使用stoi(String to int)函数或只使用Stringstream,这是进行单独转换的第三种方式,代码如下:

第一种方法

std::string s1 = "4533";
std::string s2 = "3.010101";
std::string s3 = "31337 with some string";

int myint1 = std::stoi(s1);
int myint2 = std::stoi(s2);
int myint3 = std::stoi(s3);

std::cout <<  s1 <<"=" << myint1 << '\n';
std::cout <<  s2 <<"=" << myint2 << '\n';
std::cout <<  s3 <<"=" << myint3 << '\n';

第二种方法

#include <string.h>
#include <sstream>
#include <iostream>
#include <cstring>
using namespace std;


int StringToInteger(string NumberAsString)
{
    int NumberAsInteger;
    stringstream ss;
    ss << NumberAsString;
    ss >> NumberAsInteger;
    return NumberAsInteger;
}
int main()
{
    string NumberAsString;
    cin >> NumberAsString;
    cout << StringToInteger(NumberAsString) << endl;
    return 0;
} 

第三种方法-但不适用于单个转换

std::string str4 = "453";
int i = 0, in=0; // 453 as on
for ( i = 0; i < str4.length(); i++)
{

    in = str4[i];
    cout <<in-48 ;

}

1

我喜欢丹的回答,尤其是因为避免了例外。对于嵌入式系统开发和其他低级系统开发,可能没有适当的异常框架可用。

在有效字符串后添加了空格检查...这三行

    while (isspace(*end)) {
        end++;
    }


也增加了检查错误的检查。

    if ((errno != 0) || (s == end)) {
        return INCONVERTIBLE;
    }


这是完整的功能。

#include <cstdlib>
#include <cerrno>
#include <climits>
#include <stdexcept>

enum STR2INT_ERROR { SUCCESS, OVERFLOW, UNDERFLOW, INCONVERTIBLE };

STR2INT_ERROR str2long (long &l, char const *s, int base = 0)
{
    char *end = (char *)s;
    errno = 0;

    l = strtol(s, &end, base);

    if ((errno == ERANGE) && (l == LONG_MAX)) {
        return OVERFLOW;
    }
    if ((errno == ERANGE) && (l == LONG_MIN)) {
        return UNDERFLOW;
    }
    if ((errno != 0) || (s == end)) {
        return INCONVERTIBLE;
    }    
    while (isspace((unsigned char)*end)) {
        end++;
    }

    if (*s == '\0' || *end != '\0') {
        return INCONVERTIBLE;
    }

    return SUCCESS;
}

@chux添加了代码来解决您提到的问题。
pellucide

1)仍然无法检测到类似的输入错误" "strtol()errno发生转换时未指定设置。更好地用于if (s == end) return INCONVERTIBLE; 检测没有转换。然后 if (*s == '\0' || *end != '\0')可以简化为if (*end)2)|| l > LONG_MAX|| l < LONG_MIN没有任何目的-它们永远都不是真的。
chux-恢复莫妮卡

@chux在Mac上,为解析错误设置了errno,但在Linux上未设置errno。更改了代码以依靠“ end”指针进行检测。
pellucide's

0

您可以使用此定义的方法。

#define toInt(x) {atoi(x.c_str())};

如果要从String转换为Integer,则只需执行以下操作。

int main()
{
string test = "46", test2 = "56";
int a = toInt(test);
int b = toInt(test2);
cout<<a+b<<endl;
}

输出为102。


4
idk。atoi鉴于其他答案(例如accepted),围绕着编写定义宏似乎并不像“ C ++方式” std::stoi()
尤金·横田

我发现使用预定义方法会更有趣:P
Boris 2014年

0

我知道这是一个比较老的问题,但是我遇到过很多次了,到目前为止,仍然没有找到一个具有以下特征的模板化解决方案:

  • 可以转换任何碱基(并检测碱基类型)
  • 将检测错误的数据(即确保整个字符串被转换占用,较少的前导/尾随空格)
  • 将确保,无论转换为哪种类型,字符串值的范围都是可接受的。

所以,这是我的,带有测试带。因为它在幕后使用了C函数strtoull / strtoll,所以它总是首先转换为可用的最大类型。然后,如果您没有使用最大的类型,它将执行其他范围检查以确认您的类型没有溢出。为此,它的性能要比正确选择strtol / strtoul的性能差一点。但是,它也适用于短裤/炭,据我所知,也没有标准库函数可以执行此操作。

请享用; 希望有人发现它有用。

#include <cstdlib>
#include <cerrno>
#include <limits>
#include <stdexcept>
#include <sstream>

static const int DefaultBase = 10;

template<typename T>
static inline T CstrtoxllWrapper(const char *str, int base = DefaultBase)
{
    while (isspace(*str)) str++; // remove leading spaces; verify there's data
    if (*str == '\0') { throw std::invalid_argument("str; no data"); } // nothing to convert

    // NOTE:  for some reason strtoull allows a negative sign, we don't; if
    //          converting to an unsigned then it must always be positive!
    if (!std::numeric_limits<T>::is_signed && *str == '-')
    { throw std::invalid_argument("str; negative"); }

    // reset errno and call fn (either strtoll or strtoull)
    errno = 0;
    char *ePtr;
    T tmp = std::numeric_limits<T>::is_signed ? strtoll(str, &ePtr, base)
                                              : strtoull(str, &ePtr, base);

    // check for any C errors -- note these are range errors on T, which may
    //   still be out of the range of the actual type we're using; the caller
    //   may need to perform additional range checks.
    if (errno != 0) 
    {
            if (errno == ERANGE) { throw std::range_error("str; out of range"); }
            else if (errno == EINVAL) { throw std::invalid_argument("str; EINVAL"); }
            else { throw std::invalid_argument("str; unknown errno"); }
    }

    // verify everything converted -- extraneous spaces are allowed
    if (ePtr != NULL)
    {
            while (isspace(*ePtr)) ePtr++;
            if (*ePtr != '\0') { throw std::invalid_argument("str; bad data"); }
    }

    return tmp;
}

template<typename T>
T StringToSigned(const char *str, int base = DefaultBase)
{
    static const long long max = std::numeric_limits<T>::max();
    static const long long min = std::numeric_limits<T>::min();

    long long tmp = CstrtoxllWrapper<typeof(tmp)>(str, base); // use largest type

    // final range check -- only needed if not long long type; a smart compiler
    //   should optimize this whole thing out
    if (sizeof(T) == sizeof(tmp)) { return tmp; }

    if (tmp < min || tmp > max)
    {
            std::ostringstream err;
            err << "str; value " << tmp << " out of " << sizeof(T) * 8
                << "-bit signed range (";
            if (sizeof(T) != 1) err << min << ".." << max;
            else err << (int) min << ".." << (int) max;  // don't print garbage chars
            err << ")";
            throw std::range_error(err.str());
    }

    return tmp;
}

template<typename T>
T StringToUnsigned(const char *str, int base = DefaultBase)
{
    static const unsigned long long max = std::numeric_limits<T>::max();

    unsigned long long tmp = CstrtoxllWrapper<typeof(tmp)>(str, base); // use largest type

    // final range check -- only needed if not long long type; a smart compiler
    //   should optimize this whole thing out
    if (sizeof(T) == sizeof(tmp)) { return tmp; }

    if (tmp > max)
    {
            std::ostringstream err;
            err << "str; value " << tmp << " out of " << sizeof(T) * 8
                << "-bit unsigned range (0..";
            if (sizeof(T) != 1) err << max;
            else err << (int) max;  // don't print garbage chars
            err << ")";
            throw std::range_error(err.str());
    }

    return tmp;
}

template<typename T>
inline T
StringToDecimal(const char *str, int base = DefaultBase)
{
    return std::numeric_limits<T>::is_signed ? StringToSigned<T>(str, base)
                                             : StringToUnsigned<T>(str, base);
}

template<typename T>
inline T
StringToDecimal(T &out_convertedVal, const char *str, int base = DefaultBase)
{
    return out_convertedVal = StringToDecimal<T>(str, base);
}

/*============================== [ Test Strap ] ==============================*/ 

#include <inttypes.h>
#include <iostream>

static bool _g_anyFailed = false;

template<typename T>
void TestIt(const char *tName,
            const char *s, int base,
            bool successExpected = false, T expectedValue = 0)
{
    #define FAIL(s) { _g_anyFailed = true; std::cout << s; }

    T x;
    std::cout << "converting<" << tName << ">b:" << base << " [" << s << "]";
    try
    {
            StringToDecimal<T>(x, s, base);
            // get here on success only
            if (!successExpected)
            {
                    FAIL(" -- TEST FAILED; SUCCESS NOT EXPECTED!" << std::endl);
            }
            else
            {
                    std::cout << " -> ";
                    if (sizeof(T) != 1) std::cout << x;
                    else std::cout << (int) x;  // don't print garbage chars
                    if (x != expectedValue)
                    {
                            FAIL("; FAILED (expected value:" << expectedValue << ")!");
                    }
                    std::cout << std::endl;
            }
    }
    catch (std::exception &e)
    {
            if (successExpected)
            {
                    FAIL(   " -- TEST FAILED; EXPECTED SUCCESS!"
                         << " (got:" << e.what() << ")" << std::endl);
            }
            else
            {
                    std::cout << "; expected exception encounterd: [" << e.what() << "]" << std::endl;
            }
    }
}

#define TEST(t, s, ...) \
    TestIt<t>(#t, s, __VA_ARGS__);

int main()
{
    std::cout << "============ variable base tests ============" << std::endl;
    TEST(int, "-0xF", 0, true, -0xF);
    TEST(int, "+0xF", 0, true, 0xF);
    TEST(int, "0xF", 0, true, 0xF);
    TEST(int, "-010", 0, true, -010);
    TEST(int, "+010", 0, true, 010);
    TEST(int, "010", 0, true, 010);
    TEST(int, "-10", 0, true, -10);
    TEST(int, "+10", 0, true, 10);
    TEST(int, "10", 0, true, 10);

    std::cout << "============ base-10 tests ============" << std::endl;
    TEST(int, "-010", 10, true, -10);
    TEST(int, "+010", 10, true, 10);
    TEST(int, "010", 10, true, 10);
    TEST(int, "-10", 10, true, -10);
    TEST(int, "+10", 10, true, 10);
    TEST(int, "10", 10, true, 10);
    TEST(int, "00010", 10, true, 10);

    std::cout << "============ base-8 tests ============" << std::endl;
    TEST(int, "777", 8, true, 0777);
    TEST(int, "-0111 ", 8, true, -0111);
    TEST(int, "+0010 ", 8, true, 010);

    std::cout << "============ base-16 tests ============" << std::endl;
    TEST(int, "DEAD", 16, true, 0xDEAD);
    TEST(int, "-BEEF", 16, true, -0xBEEF);
    TEST(int, "+C30", 16, true, 0xC30);

    std::cout << "============ base-2 tests ============" << std::endl;
    TEST(int, "-10011001", 2, true, -153);
    TEST(int, "10011001", 2, true, 153);

    std::cout << "============ irregular base tests ============" << std::endl;
    TEST(int, "Z", 36, true, 35);
    TEST(int, "ZZTOP", 36, true, 60457993);
    TEST(int, "G", 17, true, 16);
    TEST(int, "H", 17);

    std::cout << "============ space deliminated tests ============" << std::endl;
    TEST(int, "1337    ", 10, true, 1337);
    TEST(int, "   FEAD", 16, true, 0xFEAD);
    TEST(int, "   0711   ", 0, true, 0711);

    std::cout << "============ bad data tests ============" << std::endl;
    TEST(int, "FEAD", 10);
    TEST(int, "1234 asdfklj", 10);
    TEST(int, "-0xF", 10);
    TEST(int, "+0xF", 10);
    TEST(int, "0xF", 10);
    TEST(int, "-F", 10);
    TEST(int, "+F", 10);
    TEST(int, "12.4", 10);
    TEST(int, "ABG", 16);
    TEST(int, "10011002", 2);

    std::cout << "============ int8_t range tests ============" << std::endl;
    TEST(int8_t, "7F", 16, true, std::numeric_limits<int8_t>::max());
    TEST(int8_t, "80", 16);
    TEST(int8_t, "-80", 16, true, std::numeric_limits<int8_t>::min());
    TEST(int8_t, "-81", 16);
    TEST(int8_t, "FF", 16);
    TEST(int8_t, "100", 16);

    std::cout << "============ uint8_t range tests ============" << std::endl;
    TEST(uint8_t, "7F", 16, true, std::numeric_limits<int8_t>::max());
    TEST(uint8_t, "80", 16, true, std::numeric_limits<int8_t>::max()+1);
    TEST(uint8_t, "-80", 16);
    TEST(uint8_t, "-81", 16);
    TEST(uint8_t, "FF", 16, true, std::numeric_limits<uint8_t>::max());
    TEST(uint8_t, "100", 16);

    std::cout << "============ int16_t range tests ============" << std::endl;
    TEST(int16_t, "7FFF", 16, true, std::numeric_limits<int16_t>::max());
    TEST(int16_t, "8000", 16);
    TEST(int16_t, "-8000", 16, true, std::numeric_limits<int16_t>::min());
    TEST(int16_t, "-8001", 16);
    TEST(int16_t, "FFFF", 16);
    TEST(int16_t, "10000", 16);

    std::cout << "============ uint16_t range tests ============" << std::endl;
    TEST(uint16_t, "7FFF", 16, true, std::numeric_limits<int16_t>::max());
    TEST(uint16_t, "8000", 16, true, std::numeric_limits<int16_t>::max()+1);
    TEST(uint16_t, "-8000", 16);
    TEST(uint16_t, "-8001", 16);
    TEST(uint16_t, "FFFF", 16, true, std::numeric_limits<uint16_t>::max());
    TEST(uint16_t, "10000", 16);

    std::cout << "============ int32_t range tests ============" << std::endl;
    TEST(int32_t, "7FFFFFFF", 16, true, std::numeric_limits<int32_t>::max());
    TEST(int32_t, "80000000", 16);
    TEST(int32_t, "-80000000", 16, true, std::numeric_limits<int32_t>::min());
    TEST(int32_t, "-80000001", 16);
    TEST(int32_t, "FFFFFFFF", 16);
    TEST(int32_t, "100000000", 16);

    std::cout << "============ uint32_t range tests ============" << std::endl;
    TEST(uint32_t, "7FFFFFFF", 16, true, std::numeric_limits<int32_t>::max());
    TEST(uint32_t, "80000000", 16, true, std::numeric_limits<int32_t>::max()+1);
    TEST(uint32_t, "-80000000", 16);
    TEST(uint32_t, "-80000001", 16);
    TEST(uint32_t, "FFFFFFFF", 16, true, std::numeric_limits<uint32_t>::max());
    TEST(uint32_t, "100000000", 16);

    std::cout << "============ int64_t range tests ============" << std::endl;
    TEST(int64_t, "7FFFFFFFFFFFFFFF", 16, true, std::numeric_limits<int64_t>::max());
    TEST(int64_t, "8000000000000000", 16);
    TEST(int64_t, "-8000000000000000", 16, true, std::numeric_limits<int64_t>::min());
    TEST(int64_t, "-8000000000000001", 16);
    TEST(int64_t, "FFFFFFFFFFFFFFFF", 16);
    TEST(int64_t, "10000000000000000", 16);

    std::cout << "============ uint64_t range tests ============" << std::endl;
    TEST(uint64_t, "7FFFFFFFFFFFFFFF", 16, true, std::numeric_limits<int64_t>::max());
    TEST(uint64_t, "8000000000000000", 16, true, std::numeric_limits<int64_t>::max()+1);
    TEST(uint64_t, "-8000000000000000", 16);
    TEST(uint64_t, "-8000000000000001", 16);
    TEST(uint64_t, "FFFFFFFFFFFFFFFF", 16, true, std::numeric_limits<uint64_t>::max());
    TEST(uint64_t, "10000000000000000", 16);

    std::cout << std::endl << std::endl
              << (_g_anyFailed ? "!! SOME TESTS FAILED !!" : "ALL TESTS PASSED")
              << std::endl;

    return _g_anyFailed;
}

StringToDecimal是用户土地方法;它是重载的,因此可以这样调用:

int a; a = StringToDecimal<int>("100");

或这个:

int a; StringToDecimal(a, "100");

我讨厌重复int类型,因此更喜欢后者。这样可以确保,如果'a'的类型更改,则不会产生不良结果。我希望编译器可以像这样解决:

int a; a = StringToDecimal("100");

...但是,C ++无法推断出模板的返回类型,所以这是我所能得到的最好的结果。

实现非常简单:

CstrtoxllWrapper同时包装strtoullstrtoll,并根据模板类型的签名状态调用所需的值,并提供一些其他保证(例如,如果未签名,则不允许使用负输入,这样可以确保整个字符串都已转换)。

CstrtoxllWrapper用于通过StringToSignedStringToUnsigned具有最大的类型(长长/无符号长长)提供给编译器; 这允许执行最大的转换。然后,如果有必要,StringToSigned/ StringToUnsigned对基础类型执行最终范围检查。最后,端点StringToDecimal方法根据基础类型的签名确定要调用哪种StringTo *模板方法。

我认为大多数垃圾邮件都可以通过编译器进行优化。几乎所有内容都应在编译时确定。关于这方面的任何评论对我来说都会很有趣!


“使用最大类型”->为什么long long而不是intmax_t
chux-恢复莫妮卡

有信心要if (ePtr != str)。此外,还可isspace((unsigned char) *ePtr)用来正确处理的负值*ePtr
chux-恢复莫妮卡

-3

在C中,您可以使用int atoi (const char * str)

解析C字符串str,将其内容解释为整数,并将其作为int类型的值返回。


2
当我链接到atoi问题时,我已经知道了。问题显然不在于C,而在于C ++。-1
尤金(Eugene Yokota)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.