将一个变量与多个值进行比较的最有效方法?

Most efficient way to compare a variable to multiple values?

提问人:Matt Reynolds 提问时间:3/3/2013 最后编辑:cigienMatt Reynolds 更新时间:1/15/2022 访问量:109135

问:

在我的程序中,有几次我不得不检查变量是否是众多选项之一。例如

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))
C++ 变量 if 语句 比较

评论

4赞 chris 3/3/2013
打字是还是打字太多?如果它更长,你总是可以制作某种数组并使用 .if (num == 1 || num == 2 || num == 3)if (num >= 1 && num <= 3)std::find
2赞 Seth Carnegie 3/3/2013
查看 stackoverflow.com/q/14368525/726361

答:

9赞 john.pavan 3/3/2013 #1

您必须对每个值进行比较。例如

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

您可能还想考虑转换并故意失败(尽管我认为这不是您所说的最佳解决方案)。

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

    default:
        //do nothing.
}

评论

1赞 Matt Reynolds 3/3/2013
是的,不完全是我要找的。无论如何,谢谢!
3赞 einpoklum 11/26/2016
嗯,不,你不必那样做。
0赞 underscore_d 9/20/2018
switch但是,只能比较整数或枚举器类型,因此,尽管它确实适用于此类对象,并且通常很整齐,但它很快就会发现自己是一招鲜,是汇编程序跳表时代的遗物,而不是通用解决方案(因此不适合泛型代码,这非常重要)。
6赞 Sergey Kalinichenko 3/3/2013 #2

您可以定义一组整数,向其添加所需的值,然后使用 find 方法查看相关值是否在集合中

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

评论

0赞 Matt Reynolds 3/3/2013
这是一种有趣的做事方式......我会试一试,谢谢!
0赞 Matt Reynolds 3/3/2013
对不起,我只是遇到了很多错误。你到底如何使用它?你能举个例子吗?
2赞 6/28/2014
哈哈,天才的简单!@TheWalkingCactus改用 .countset
2赞 underscore_d 12/10/2019
@Sahsahae 我不同意。对于一次性与一小组已知值进行比较的情况,集合或任何其他动态分配的容器是浪费的,添加全局变量是不负责任的。对于实际提出的问题,与一组已知常数相比,使用折叠表达式的可调参数函数要好得多。in()
2赞 Adrian Mole 9/3/2021
请注意,从 C++20 开始,有 std::set.contains()。
13赞 Eric Johnson 3/3/2013 #3

如果要检查的值足够小,则可以创建要查找的值的位掩码,然后检查要设置的位。

假设,您关心几个组。

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,但可能会因环境的具体情况而异。

评论

0赞 Matt Reynolds 3/3/2013
凉。这让事情变得简单。如果我想看“如果 1;else if 2, else if 3;“我该怎么做?我需要一个新的位掩码,还是可以添加“(2 << n)”?
0赞 Eric Johnson 3/3/2013
如果要检查值 1、2 和 3,则位掩码将变为 values_i_like = (1<<1) |(1<<2) |(1<<3);。
0赞 Matt Reynolds 3/3/2013
不,你误会了。我希望能够与 (1,2,3) 和 (4,5,6) 不同
0赞 Eric Johnson 3/3/2013
values_i_like = (1<<1) |(1<<2) |(1<<3) |(1<<4) |(1<<5) |(1<<6);
0赞 Matt Reynolds 3/3/2013
但是你如何区分(1,2,3)和(4,5,6)呢?
74赞 Nikos C. 3/3/2013 #4

这是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 }

但是,当不与内置类型一起使用时,它不是很有效。 可以正常工作,但是例如,如果您比较变量,则生成的代码非常糟糕。intstd::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 版本在这里效率非常低,而这个版本应该产生与手写比较相同的代码。

评论

0赞 Matt Reynolds 3/3/2013
这看起来很方便,但我没有使用 11。我只是普通的 ol' C++。
21赞 underscore_d 9/20/2018
+1 表示在 C++ 中执行此操作的惊人好方法17。如果可以的话,我会+2,因为你费心在5.5年后回来并添加它!
9赞 GetFree 10/2/2018
c++17 功能称为折叠表达式
2赞 Kai Petzke 3/18/2019
模板内联功能很棒!我建议使用以下签名声明它:.如果你的任何类中确实有一个,它需要接收至少一个参数作为可写的右值引用,那么签名将是正确的,但函数体中的参数应该通过 和 转发。is_inbool is_in(const First& first, const T& ... t)operator==std::forward<First>(first)std::forward<T>(t)
0赞 underscore_d 12/10/2019
@KaiPetzke对。完美的转发在这里似乎没有必要,因为可以很好地绑定到临时人员,并根据需要延长其生命周期。const&
8赞 Felix Petriconi 1/24/2015 #5

我刚刚遇到了类似的问题,我得出了这些 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赞 Ruslan 6/21/2020
您不必复制到 : 结构可以简单地保存引用。Td_Is
5赞 Darren Smith 2/11/2017 #6

我需要为枚举做类似的事情。我有一个变量,并希望根据一系列值对其进行测试。

在这里,我使用了可变参数模板函数。请注意类型的专业化,以便对 when stores 有预期的结果。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赞 underscore_d 9/20/2018
C 字符串的专用化是一个很好的选择(因为很难想象您何时想要将指针与该类型进行比较的默认行为),因为存在一个简洁而实用的示例!
-3赞 Ajit saigal 4/5/2020 #7
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