Compact way to write if(..) statement with many equalities
有没有更好的方法来编写这样的代码:
1 | if (var =="first case" or var =="second case" or var =="third case" or ...) |
在python中,我可以写:
1 | if var in ("first case","second case","third case", ...) |
这也让我有机会轻松通过一系列好的选择:
1 2 | good_values ="first case","second case","third case" if var in good_values |
这只是一个例子:
职业奖金:
or 的懒惰- 编译时循环展开
- 易于扩展到除
== 以外的其他运营商
if you want to expand it compile time you can use something like this
1 2 3 4 5 6 7 8 9 10 11 12 13 | template<class T1, class T2> bool isin(T1&& t1, T2&& t2) { return t1 == t2; } template<class T1, class T2, class... Ts> bool isin(T1&& t1 , T2&& t2, T2&&... ts) { return t1 == t2 || isin(t1, ts...); } std::string my_var = ...; // somewhere in the code ... bool b = isin(my_var,"fun","gun","hun"); |
实际上,我并没有测试它,这个想法来自亚历山德里斯科的"变数模板是功能性的"谈话。因此,对于细节(以及适当的实现),请注意这一点。
编辑:在C++ 17中,他们引入了一个很好的折叠表达式语法。
1 2 3 4 5 6 7 | template<typename... Args> bool all(Args... args) { return (... && args); } bool b = all(true, true, true, false); // within all(), the unary left fold expands as // return ((true && true) && true) && false; // b is false |
1 2 3 4 5 6 7 | #include #include <initializer_list> auto tokens = {"abc","def","ghi" }; bool b = std::any_of(tokens.begin(), tokens.end(), [&var](const char * s) { return s == var; }); |
(您可能希望将
或者创建包装模板:
1 2 3 4 5 6 7 8 9 | #include #include <initializer_list> #include <utility> template <typename T, typename F> bool any_of_c(const std::initializer_list<T> & il, F && f) { return std::any_of(il.begin(), il.end(), std::forward<F>(f)); } |
用途:
1 2 | bool b = any_of_c({"abc","def","ghi <div class="suo-content">[collapse title=""]<ul><li>小心!你的陈述是在比较指针!您可能想使用<wyn>std::strcmp</wyn>或其亲属之一。</li><li>我可能不能很好地阅读C++的GO,但这又是如何让GoO知道如何将两个EDCOX1与3=操作符进行比较呢?也许可以避免goo,写一个简单的,无bug的for循环…</li><li>请注意,原始代码中不清楚<wyn>var</wyn>的类型。如果它也是一个<wyn>char *</wyn>(而不是,比如说,<wyn>std::string</wyn>),那么OP也会有类似的问题。</li><li>@伦丁,很有可能将指针与<wyn>==</wyn>进行比较。然而,这可能不是这里需要的。</li><li>@我的意思是,它是如何比较内容而不是地址的?</li><li>@Joshuagreen:<wyn>val</wyn>当然是一个<wyn>std::string_view</wyn>。(想到它,初始值列表应该是一个字符串视图列表,可以一次性解决问题。)不管怎样,这就是OP要求的。我想他知道他的比较的语义学。</li><li>@Lundin,它会比较地址。所以我觉得这里不对。</li><li>那么这个bug是用<wyn>auto</wyn>创建的?或者自动意味着你得到了std::string而不是<wyn>const char*</wyn>?为什么要在一开始就用这些废话来做一些微不足道的事情呢?它的设计似乎是为了打破程序,制造出细微的错误。我真的需要把C++标签添加到我的忽略列表中…</li><li>@实际上,我对这个问题的模棱两可的表述有些困惑。在上面的答案中,lambda引用<wyn>var</wyn>和tokens作为<wyn>const char *</wyn>s。从表达式中,我们不能确定<wyn>var</wyn>是什么,我们只知道它必须与<wyn>const char *</wyn>s相比较。如果<wyn>var</wyn>是<wyn>std::string</wyn>那么<wyn>std::string</wyn>s将从<wyn>const char *</wyn>s构造,所有东西都将Rk如预期。如果<wyn>var</wyn>是<wyn>char *</wyn>,那么<wyn>==</wyn>将简单地比较指针。</li><li>@伦丁:虽然弦和指针的主题很吸引人,但我认为它与这个问题没有任何关系。操作开始时使用了与我在答案中提供的完全相同的比较语法。答案显示了代码结构的模型方法。我相信OP可以将比较的细节与他实际问题使用的任何实际类型相适应。所有这些都与问题(和答案)是正交的。</li><li>@Kerreksb,很公平,但是如果有人搜索字符串比较方法并找到了这篇文章,我不希望他们被那个问题咬到。</li><li>但是你从这些混乱中得到了什么呢?为什么不能使用简单的for循环?给我一个好理由。</li><li>@伦丁:原始代码是读者的负担。它迫使读者阅读你的循环体。为概念命名并进行命名调用可以让读者跳过"了解您的意思"这一行,并相信经过测试的库实现已经正确地实现了意图。从根本上讲,每一个系统都受到其日益增长的复杂性的限制;命名的抽象(算法)是降低复杂性的主要策略之一。</li><li>如果读者是如此令人难以置信的无能以至于他们甚至不能理解最简单的<wyn>for</wyn>循环,那么是什么让你认为他们能更好地理解所有这些元编程的废话呢?</li><li>@伦丁:这与能力无关。它是关于分解复杂性的。读者有比看你的循环更好的事情要做。</li><li>如果你认为读者会解释一个简单的,完全基础的,2秒理解的,单循环条件比他们解释你所有的元编程所需的时间要慢,那么与你进一步的争论显然是毫无意义的。</li><li>仅供参考:您的包装模板不能这样编译-不能从一个有支撑的init列表中推断(不幸的是)。</li><li>@巴里:没错,谢谢。我来解决这个问题。</li><li>@Lundin并不是说"很难理解<wyn>for</wyn>循环",而是说"很容易发现细微的错误"。例如,在开始/结束时出现1个错误(如果有基于1的而不是更常见的基于0的数组,则很容易);在更复杂的循环体中,可能会出现副作用或错误处理方面的错误。尽管for循环是微不足道的,但一般迭代背后的思想是消除某些常见错误。在琐碎的例子中,授权收益是最小的,您不希望通过引入其他错误而失去它们。但是foreach,ifany,ifall,ifnone都是简单而强大的概念。</li><li>@从上面的注释中可以看出,元代码很容易包含非常细微的错误,例如,假设"var"是<wyn>const char*</wyn>类型,而不是某个类具有该类型的重载运算符。至于消除错误,它们来自于编写复杂的代码,您可以在其中编写简单的代码。当您有一个最简单形式的循环<wyn>for(i=0; i<n; i++)</wyn>时,几乎不可能编写bug。但是如果你坐着"咀嚼空气"一个小时,在想出一个相当于这个循环的元程序之前,你很可能会写错误。</li><li>许多有成就的程序员提倡在他们的书中使用这些小函数,这是其易于(单元)测试的主要原因之一,其他的原因是使用它的代码可读性更高,不会破坏其抽象级别,代码重复更少,更容易扩展/更改。我现在读到的一个来源是:"现代C++编程与测试驱动开发"的Jeff Langr,他采取了非常远,并提供令人信服的理由和例子这样做,绝对值得一读iMo。还要检查鲍勃叔叔写的"清洁代码"。</li></ul>[/collapse]</div><p><center>[wp_ad_camp_1]</center></p><hr><P>好吧,那么,你需要彻底的语言修改。具体来说,您希望创建自己的运算符。准备好了吗?</P><P>句法</P><P>我将修改语法以使用C和C++样式列表:</P>[cc lang="cpp"]if (x in {x0, ...}) ... |
此外,我们将让我们的新In运算符应用于定义了
1 | if (x in my_vector) ... |
有一个警告:它不是一个真正的运算符,因此它必须始终用括号括起来作为它自己的表达式:
1 2 3 | bool ok = (x in my_array); my_function( (x in some_sequence) ); |
代码
首先要注意的是,RLM通常需要一些宏和运算符滥用。幸运的是,对于一个简单的成员资格谓词,滥用实际上并没有那么严重。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | #ifndef DUTHOMHAS_IN_OPERATOR_HPP #define DUTHOMHAS_IN_OPERATOR_HPP #include #include <initializer_list> #include <iterator> #include <type_traits> #include <vector> //---------------------------------------------------------------------------- // The 'in' operator is magically defined to operate on any container you give it #define in , in_container() = //---------------------------------------------------------------------------- // The reverse-argument membership predicate is defined as the lowest-precedence // operator available. And conveniently, it will not likely collide with anything. template <typename T, typename Container> typename std::enable_if <!std::is_same <Container, T> ::value, bool> ::type operator , ( const T& x, const Container& xs ) { using std::begin; using std::end; return std::find( begin(xs), end(xs), x ) != end(xs); } template <typename T, typename Container> typename std::enable_if <std::is_same <Container, T> ::value, bool> ::type operator , ( const T& x, const Container& y ) { return x == y; } //---------------------------------------------------------------------------- // This thunk is used to accept any type of container without need for // special syntax when used. struct in_container { template <typename Container> const Container& operator = ( const Container& container ) { return container; } template <typename T> std::vector <T> operator = ( std::initializer_list <T> xs ) { return std::vector <T> ( xs ); } }; #endif |
用法
伟大的!现在,我们可以以您期望的所有方式使用它,一个in操作符是有用的。根据您的特殊兴趣,请参见示例3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | #include <iostream> #include <set> #include <string> using namespace std; void f( const string& s, const vector <string> & ss ) { cout <<"nope "; } void f( bool b ) { cout <<"fooey! "; } int main() { cout << "I understand three primes by digit or by name. " "Type "q" to "quit". "; while (true) { string s; cout <<"s?"; getline( cin, s ); // Example 1: arrays const char* quits[] = {"quit","q" }; if (s in quits) break; // Example 2: vectors vector <string> digits {"2","3","5" }; if (s in digits) { cout <<"a prime digit "; continue; } // Example 3: literals if (s in {"two","three","five"}) { cout <<"a prime name! "; continue; } // Example 4: sets set <const char*> favorites{"7","seven" }; if (s in favorites) { cout <<"a favorite prime! "; continue; } // Example 5: sets, part deux if (s in set <string> {"TWO","THREE","FIVE","SEVEN" }) { cout <<"(ouch! don't shout!) "; continue; } // Example 6: operator weirdness if (s[0] in string("014") +"689") { cout <<"not prime "; continue; } // Example 7: argument lists unaffected f( s, digits ); } cout <<"bye "; } |
潜在的改进
为了您的特定目的,总是可以做一些事情来改进代码。可以添加ni(不在)运算符(添加新的thunk容器类型)。您可以在名称空间中包装thunk容器(一个好主意)。你可以专门研究像
你的其他顾虑
const 对mutable 不是问题;两者都可用于操作员- EDOCX1的懒惰(6):从技术上讲,
or 不是懒惰,而是短路。std::find() 算法也以同样的方式短路。 - 编译时循环展开:这里不太适用。您的原始代码没有使用循环;虽然
std::find() 使用循环,但任何可能发生的循环展开都取决于编译器。 - 很容易扩展到
== 以外的操作符:这实际上是一个单独的问题;您不再关注简单的成员谓词,而是考虑使用函数折叠过滤器。完全可以创建这样做的算法,但是标准库提供了any_of() 函数,它正好做到了这一点。(它不像我们的RLM"In"接线员那么漂亮。也就是说,任何C++程序员都会很容易理解它。这里已经给出了这样的答案。)
希望这有帮助。
First, I recommend using a
for loop, which is both the easiest and
most readable solution:
1
2
3
4
5
6 for (i = 0; i < n; i++) {
if (var == eq[i]) {
// if true
break;
}
}
但是,也有一些其他方法可用,如
让我们来看一个简单的示例程序,它包含所有上述关键字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include <vector> #include <numeric> #include #include <iterator> #include <iostream> #include <functional> int main() { std::vector<int> v(10, 2); std::partial_sum(v.cbegin(), v.cend(), v.begin()); std::cout <<"Among the numbers:"; std::copy(v.cbegin(), v.cend(), std::ostream_iterator<int>(std::cout,"")); std::cout << '\ '; if (std::all_of(v.cbegin(), v.cend(), [](int i){ return i % 2 == 0; })) { std::cout <<"All numbers are even\ "; } if (std::none_of(v.cbegin(), v.cend(), std::bind(std::modulus<int>(), std::placeholders::_1, 2))) { std::cout <<"None of them are odd\ "; } struct DivisibleBy { const int d; DivisibleBy(int n) : d(n) {} bool operator()(int n) const { return n % d == 0; } }; if (std::any_of(v.cbegin(), v.cend(), DivisibleBy(7))) { std::cout <<"At least one number is divisible by 7\ "; } } |
您可以使用std::set来测试var是否属于它。(用C++ 11启用编译)
1 2 3 4 5 6 7 8 9 10 11 12 | #include <iostream> #include <set> int main() { std::string el ="abc"; if (std::set<std::string>({"abc","def","ghi"}).count(el)) std::cout <<"abc belongs to {"abc", "def", "ghi"}" << std::endl; return 0; } |
其优点是,与非紧凑型
1 | static std::set<std::string> the_set = {"abc","def","ghi"}; |
但是,在我看来,最好还是保持原样,除非它包含10个以上的字符串进行检查。在这种测试中使用std::set的性能优势只出现在大的
最接近的事情是:
1 2 3 4 5 | template <class K, class U, class = decltype(std::declval<K>() == std::declval<U>())> bool in(K&& key, std::initializer_list<U> vals) { return std::find(vals.begin(), vals.end(), key) != vals.end(); } |
我们需要一个
我们可以这样使用:
1 2 3 | std::string var ="hi"; bool b = in(var, {"abc","def","ghi","hi"}); std::cout << b << std::endl; // true |
如果你可以访问C++ 14(不确定这是否与C++ 11一起工作),你可以写出这样的东西:
1 2 3 4 5 | template <typename T, typename L = std::initializer_list<T>> constexpr bool is_one_of(const T& value, const L& list) { return std::any_of(std::begin(list), std::end(list), [&value](const T& element) { return element == value; }); }; |
呼叫如下:
1 2 | std::string test_case = ...; if (is_one_of<std::string>(test_case, {"first case","second case","third case" })) {...} |
或者像这样
1 2 3 | std::string test_case = ...; std::vector<std::string> allowedCases{"first case","second case","third case" }; if (is_one_of<std::string>(test_case, allowedCases)) {...} |
如果您不喜欢将允许的案例"包装"成列表类型,您还可以编写一个这样的小助手函数:
1 2 3 4 5 | template <typename T, typename...L> constexpr bool is_one_of(const T& value, const T& first, const L&... next) //First is used to be distinct { return is_one_of(value, std::initializer_list<T>{first, next...}); }; |
这将允许您这样称呼它:
1 2 | std::string test_case = ...; if (is_one_of<std::string>(test_case,"first case","second case","third case" )) {...} |
关于coliru的完整示例
值得注意的是,在我见过的大多数Java和C++代码中,清单3的条件句是公认的实践。它肯定比"聪明"的解决方案更易读。如果这种情况经常发生,这是一个主要的阻力,无论如何,这是一种设计的味道,模板化或多态的方法可能有助于避免这种情况。
所以我的答案是"空"操作。继续做更详细的事情,这是最容易接受的。
你可以用开关盒。您不必列出单独的案例:
包括使用命名空间std;
In main(){char grade='b';
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | switch(grade) { case 'A' : case 'B' : case 'C' : cout <<"Well done" << endl; break; case 'D' : cout <<"You passed" << endl; break; case 'F' : cout <<"Better try again" << endl; break; default : cout <<"Invalid grade" << endl; } cout <<"Your grade is" << grade << endl; return 0; |
}
因此,您可以将结果分组在一起:A、B和C将输出"干得好"。我从教程的角度来举这个例子:http://www.tutorialspoint.com/cplusplus/cpp_switch_statement.htm