比较一个变量与多个值的最有效方法是什么?

31
在我的程序中,有几次我需要检查变量是否为多个选项之一。例如:
if (num = (<1 or 2 or 3>)) { DO STUFF }

我曾尝试使用“OR”的方式,但一切似乎都不对。我已经尝试过。

if (num == (1 || 2 || 3))

但它什么也不做。

我希望能方便地区分几个组。例如:

if (num = (1,2,3))

else if (num = (4,5,6))

else if (num = (7,8,9))

4
“如果使用if (num == 1 || num == 2 || num == 3)if (num >= 1 && num <= 3)太长了,你可以考虑创建一个数组并使用std::find,这样会更简洁。” - chris
2
请查看 https://dev59.com/7G3Xa4cB1Zd3GeqPdEyP - Seth Carnegie
7个回答

74
以下是关于C++11的一种方法,使用std::initializer_list

#include <algorithm>
#include <initializer_list>

template <typename T>
bool is_in(const T& v, std::initializer_list<T> lst)
{
    return std::find(std::begin(lst), std::end(lst), v) != std::end(lst);
}

有了那个,你可以做到:

if (is_in(num, {1, 2, 3})) { DO STUFF }

这种方式在使用非内置类型时效率不高。虽然使用int没问题,但如果比较例如std::string这样的变量,生成的代码就很糟糕。
然而,在C++17中,你可以使用一种更加高效的解决方案,适用于任何类型:
template<typename First, typename ... T>
bool is_in(First &&first, T && ... t)
{
    return ((first == t) || ...);
}

// ...

// s1, s2, s3, s4 are strings.
if (is_in(s1, s2, s3, s4)) // ...

C++11版本在这里非常低效,而此版本应该产生与手写比较相同的代码。

那看起来很方便,但我没有使用11版。我只是在使用普通的C++。 - Matt Reynolds
21
+1 对于在C++17中这样出色的方法,我感到非常惊叹。如果可以的话,我会给你+2,因为你在5.5年后回来并添加了这个! - underscore_d
9
C++17的新特性称为“折叠表达式”(fold expression)。 - GetFree
2
is_in 模板内联函数非常好用!不过我建议使用以下签名进行声明:bool is_in(const First& first, const T& ... t)。如果你的类中确实有 operator==,并且至少需要将其参数之一作为可写的右值引用接收,则该签名将是正确的,但是函数体中的参数应通过 std::forward<First>(first)std::forward<T>(t) 进行转发。 - Kai Petzke
@KaiPetzke 对的。在这里,完美转发似乎是不必要的,因为 const& 可以很好地绑定到临时对象,并将其生命周期延长至所需时间。 - underscore_d
显示剩余2条评论

13
如果您想要检查的值足够小,可以创建一个位掩码来寻找您需要的值,然后检查该位是否被设置。
假设您关心一些组。
static const unsigned values_group_1 = (1 << 1) | (1 << 2) | (1 << 3);
static const unsigned values_group_2 = (1 << 4) | (1 << 5) | (1 << 6);
static const unsigned values_group_3 = (1 << 7) | (1 << 8) | (1 << 9);    
if ((1 << value_to_check) & values_group_1) {
  // You found a match for group 1
}
if ((1 << value_to_check) & values_group_2) {
  // You found a match for group 2
}
if ((1 << value_to_check) & values_group_3) {
  // You found a match for group 3
}

这种方法最适用于不超过CPU自然处理大小的值。在现代情况下,这通常是64,但可能会因您的环境具体情况而异。

如果你想检查值1、2和3,那么你的位掩码将变成这样:values_i_like = (1<<1) | (1<<2) | (1<<3);。 - Eric Johnson
不,你误解了。我想要能够区分 (1,2,3) 和 (4,5,6)。 - Matt Reynolds
values_i_like = (1<<1) | (1<<2) | (1<<3) | (1<<4) | (1<<5) | (1<<6); 我喜欢的值=(1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<5)|(1<<6); - Eric Johnson
但是你如何区分 (1,2,3) 和 (4,5,6)? - Matt Reynolds
只需过度明确一下,您只需要在测试的“then”侧的逻辑在不同集合中不同时使用多个集合。 如果逻辑相同,则使用一个集合。 此外,如果您不熟悉位移运算符,请花些时间学习它们。 复制代码是可以的。 复制看起来像魔法的内容则不好。 - Eric Johnson
显示剩余6条评论

9

您需要与每个值进行比较。例如:

if (num == 1 || num == 2 || num == 3) { stuff }

您可能还想考虑使用switch语句并有意让其穿透到后面的情况(虽然我认为这不是您所述问题的最佳解决方案)。

switch (num) {
    case 1:
    case 2:
    case 3:
        {DO STUFF}
        break;

    default:
        //do nothing.
}

1
是的,不完全是我要找的。无论如何还是谢谢! - Matt Reynolds
3
嗯,不,你不必这样做。 - einpoklum
switch 只能比较整数或枚举类型,因此虽然它可以很好地处理这些对象,但它很快就会显露出自己是一个单一功能的工具,是汇编跳转表时代的遗物,而不是通用解决方案(因此不适用于泛型代码,这非常重要)。 - underscore_d

8

我曾经遇到过类似的问题,这里提供 C++11 的解决方案:

template <class T> 
struct Is 
{ 
  T d_; 
  bool in(T a) { 
    return a == d_; 
  } 
  template <class Arg, class... Args> 
  bool in(Arg a, Args... args) { 
    return in(a) || in(args...); 
  } 
}; 

template <class T> 
Is<T> is(T d) { 
  return Is<T>{d}; 
}

或者使用非递归终止方法作为替代方法。请注意,这里比较的顺序是未定义的,并且如果找到第一个匹配项,它不会提前终止。但代码更加紧凑。

template <class T>
struct Is {
  const T d_;
  template <class... Args>
  bool in(Args... args) {
    bool r{ false }; 
    [&r](...){}(( (r = r || d_ == args), 1)...);
    return r;
  }
};

template <class T>
Is<T> is(T d) { 
  return Is<T>{d}; 
}

因此,对于这两种解决方案,代码将如下所示:
if (is(num).in(1,2,3)) {
  // do whatever needs to be done
}

1
你不必将 T 复制到 d_ 中:Is 结构体可以简单地持有一个引用。 - Ruslan

6

你可以定义一个整数集合,将需要的值添加到其中,然后使用查找方法来查看所需值是否在该集合中。

std::set<int> values;
// add the desired values to your set...
if (values.find(target) != values.end())
    ...

2
太聪明了,简单易懂!@TheWalkingCactus只需使用countset即可。 - user1382306
可以这样做,但是对于一个如此短暂和可能频繁的任务而言,这需要大量动态分配、复制和立即销毁。另外,“检查一个值是否在集合中”是一个非常基本的概念,它(A)应该是语言的基本功能之一,(B)应该是免费的。当然也有其他人写了帮助函数来满足(B),因此可能并不需要(A)。 - underscore_d
@underscore_d,除非我们意识到全局变量存在并且在这种情况下非常完美。 - user11877195
2
@Sahsahae 我不同意。对于与一组小的已知值进行比较的情况,使用集合或任何其他动态分配的容器都是浪费的,而添加全局变量是不负责任的。对于实际提出的问题,使用可变参数的 in() 函数和折叠表达式进行比较要优越得多。 - underscore_d
2
请注意,从C++20开始,有std::set.contains() - Adrian Mole
显示剩余2条评论

5

我需要做类似于枚举的事情。 我有一个变量,希望将其与一系列值进行测试。

在这里,我使用了可变模板函数。 请注意const char*类型的特化,以便is_in(my_str,“a”,“b”,“c”)对于当my_str存储“a”时具有预期的结果。

#include <cstring> 

template<typename T>
constexpr  bool is_in(T t, T v) {
  return t == v;
}

template<>
constexpr  bool is_in(const char* t, const char* v) {
  return std::strcmp(t,v);
}

template<typename T, typename... Args>
constexpr bool is_in(T t, T v, Args... args) {
  return  t==v || is_in(t,args...);
}

使用示例:

enum class day
{
  mon, tues, wed, thur, fri, sat, sun
};

bool is_weekend(day d)
{
  return is_in(d, day::sat, day::sun);
}

1
C字符串的特化是一个不错的设计(因为很难想象你何时需要将指针的默认行为与该类型进行比较),简明实用的示例也很好! - underscore_d

-3
float n;
if (n<1) exit(0);  
if (n / 3 <= 1)  
   // within 1, 2, 3
else if (n / 3 <= 2)  
   // within 4, 5, 6  
else if (n / 3 <= 3)  
   // within 7, 8, 9

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