如何检查 C++ 字符串是否为整数?

47

使用getline时,我可能会输入一堆字符串或数字,但我只想让while循环在它不是数字的情况下输出"word"。那么有没有办法检查"word"是否是数字呢?我知道可以使用atoi()处理C-strings,但是对于string类的字符串该怎么办呢?

int main () {
  stringstream ss (stringstream::in | stringstream::out);
  string word;
  string str;
  getline(cin,str);
  ss<<str;
  while(ss>>word)
    {
      //if(    )
        cout<<word<<endl;
    }
}

2
重复的吗?https://dev59.com/J3M_5IYBdhLWcg3ww2AQ#1243435 - GManNickG
11个回答

75

另一个版本...

使用strtol,将其包装在一个简单的函数中以隐藏其复杂性:

inline bool isInteger(const std::string & s)
{
   if(s.empty() || ((!isdigit(s[0])) && (s[0] != '-') && (s[0] != '+'))) return false;

   char * p;
   strtol(s.c_str(), &p, 10);

   return (*p == 0);
}

为什么使用 strtol ?

虽然我非常喜欢 C++,但有时候在我看来 C API 是最好的选择:

  • 对于一个可能失败的测试来说,使用异常处理是过度了。
  • 当 C 标准库有一个专门的函数可以胜任工作时,使用 lexical cast 创建临时流对象是过度和低效的。

它是如何工作的?

strtol 乍一看似乎相当原始,因此解释后代码更容易阅读:

strtol 将解析字符串,停在第一个不能被视为整数的字符处。如果你提供了 p(就像我上面做的那样),它会将 p 设置在这个第一个非整数字符的位置。

我的想法是,如果 p 没有被设置为字符串的结尾(0 字符),那么该字符串中存在非整数字符,这意味着该字符串不是一个正确的整数。

第一个测试是用来排除特殊情况的(前导空格、空字符串等)。

当然,这个函数应该根据你的需求进行定制(前导空格是错误吗?等等)。

来源:

请参阅 strtol 的描述:http://en.cppreference.com/w/cpp/string/byte/strtol

也可以查看 strtol 的姐妹函数(strtodstrtoul 等)的描述。


2
迄今为止最好的答案。lexical_cast 过于复杂,而且接近不良实践。 - David Titarenco
1
@David Titarenco: lexical_cast 过于复杂,且边缘不良。只有在使用不当时才会如此,其他功能也是如此。boost::lexical_cast 的真正用途是用于通用代码或快速而简单的代码。通用代码: 当您有一个未知类型 T 时,使用 switch 使用 strtol、strtol 等是错误的。使用 boost::lexical_cast 是正确的选择,提供了 boost::lexical_cast 特定类型的特化以实现高效使用。快速而简单的代码: 如果该代码不需要速度,则使用 boost::lexical_cast 是最简单的方法。 - paercebal
你为什么会谈到异常和lexical_cast过于繁琐和低效,当你根本没有进行测量(或者如果你已经测量了,你也没有展示结果)? - Enn Michael
在我的系统上(GCC 9.2.0),我发现 !isdigit(s[0])) && (s[0] != '-') && (s[0] != '+') 的边角情况检查并不必要,因为 strtol 已经做了这个检查。但是,空字符串的检查必须存在。 - Daniel R. Collins

74

如果输入是数字加文本,那么接受的答案会产生错误的结果,因为"stol"将转换前面的数字并忽略其余部分。

我最喜欢下面的版本,因为它是一个漂亮的一行代码,不需要定义函数,而且你可以随时复制粘贴到任何地方使用。

#include <string>

...

std::string s;

bool has_only_digits = (s.find_first_not_of( "0123456789" ) == std::string::npos);

编辑:如果你喜欢这个实现方式,但是不想将其作为一个函数来使用,那么可以尝试下面的代码:

bool has_only_digits(const string s){
  return s.find_first_not_of( "0123456789" ) == string::npos;
}

2
这是一个不错的一行代码,不需要定义函数,你可以随时复制粘贴到需要的地方。但是最好还是定义一个函数,不要直接复制粘贴代码。 - Enn Michael
2
@FerdinandoRandisi 负数怎么办? - Aravind Voggu
没错,这对于负数是行不通的。但你可以检查第一个字符是+还是-,如果是这种情况,只需要传递字符串的其余部分即可。 - Ferdinando Randisi
哇,这是一个很棒的答案!我从来没有想到过。 - vincebel
1
为了考虑负数,只需在“find_first_not_of”的参数中添加“-”:s.find_first_not_of("0123456789+-") == std::string::npos。此外,如果字符串包含+42(如42)或-42,则还要加上“+”和“-”。 - Caio V.
显示剩余2条评论

17

您可以尝试使用boost::lexical_cast。如果转换失败,它会抛出一个bad_lexical_cast异常。

对于您的情况:

int number;
try
{
  number = boost::lexical_cast<int>(word);
}
catch(boost::bad_lexical_cast& e)
{
  std::cout << word << "isn't a number" << std::endl;
}

1
在字符串解析代码中抛出和捕获异常只会使一切变得非常缓慢。 - Johan

9

如果您只是想检查word是否为数字,那就不太难:

#include <ctype.h>

...

string word;
bool isNumber = true;
for(string::const_iterator k = word.begin(); k != word.end(); ++k)
    isNumber &&= isdigit(*k);

按需进行优化。


5

使用功能强大的C stdio/string函数:

int dummy_int;
int scan_value = std::sscanf( some_string.c_str(), "%d", &dummy_int);

if (scan_value == 0)
    // does not start with integer
else
    // starts with integer

如果 sscanf 返回匹配项的数量,则您的示例(注释)是错误的。或者我有什么误解吗? - Sebastian Mach

4

好的,我认为你有三种选择。

1:如果你只想检查数字是否为整数,并且不关心转换,但是希望将其保留为字符串并且不关心潜在的溢出,则在此处检查它是否与整数的正则表达式匹配最理想。

2:你可以使用boost :: lexical_cast,然后捕获可能的boost :: bad_lexical_cast异常以查看转换是否失败。如果你可以使用boost并且转换失败是一种例外情况,则这将很有效。

3:编写类似于lexical_cast的自定义函数,检查转换并根据成功与否返回true / false。如果方案1和2不符合你的要求,则这将起作用。


1
选择A。正则表达式无法告诉您是否溢出整数范围。 - Billy ONeal
原始问题并未提及想要检查溢出或者想要将数字用作整数。我所知道的是,他们可能计划将数字保留在字符串中以避免这样的限制。 - Mike DeSimone
好的,我尝试更改回复,包括我最初列出的3个选项,每个选项都有原因。 - Jacob

4

您可以使用推荐的boost :: lexical_cast,但是如果您已经了解字符串(即,如果字符串包含整数文字,则不会有任何前导空格,或者整数没有指数写法),那么编写自己的函数应该更有效率,而且并不特别困难。


3
这里有另一个解决方案。
try
{
  (void) std::stoi(myString); //cast to void to ignore the return value   
  //Success! myString contained an integer
} 
catch (const std::logic_error &e)
{   
  //Failure! myString did not contain an integer
}

3
异常不应该用于正常操作和流程控制。它们只应该用于...你猜对了:异常情况。 ;) - JHBonarius
感谢您的反馈! - Benjamin
1
警告:无论如何都不会起作用,std::stoi不会抛出异常,在失败的情况下将返回0。 - Dado

3

自从C++11以来,您可以使用std::all_of::isdigit

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string_view>

int main([[maybe_unused]] int argc, [[maybe_unused]] char *argv[])
{
    auto isInt = [](std::string_view str) -> bool {
        return std::all_of(str.cbegin(), str.cend(), ::isdigit);
    };

    for(auto &test : {"abc", "123abc", "123.0", "+123", "-123", "123"}) {
        std::cout << "Is '" << test << "' numeric? " 
            << (isInt(test) ? "true" : "false") << std::endl;
    }

    return 0;
}

使用 Godbolt查看结果。


2

我已经修改了 paercebal的方法 以满足我的需求:

typedef std::string String;    

bool isInt(const String& s, int base){
   if(s.empty() || std::isspace(s[0])) return false ;
   char * p ;
   strtol(s.c_str(), &p, base) ;
   return (*p == 0) ;
}


bool isPositiveInt(const String& s, int base){
   if(s.empty() || std::isspace(s[0]) || s[0]=='-') return false ;
   char * p ;
   strtol(s.c_str(), &p, base) ;
   return (*p == 0) ;
}


bool isNegativeInt(const String& s, int base){
   if(s.empty() || std::isspace(s[0]) || s[0]!='-') return false ;
   char * p ;
   strtol(s.c_str(), &p, base) ;
   return (*p == 0) ;
}

注意:

  1. 您可以检查各种进制(二进制、八进制、十六进制等)。
  2. 请确保不要将1、负数或>36的值作为基数传递。
  3. 如果将0作为基数传递,则会自动检测基数,即以0x开头的字符串将被视为十六进制,以0开头的字符串将被视为八进制。字符不区分大小写。
  4. 字符串中的任何空格都将使其返回false。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接