有没有更短的方式来写复合“如果”条件?
而不是 :
if ( ch == 'A' || ch == 'B' || ch == 'C' || .....
例如,要这样做:
if ( ch == 'A', 'B', 'C', ...
有没有更简单的方式来总结条件?
strchr()
可用于查看该字符是否在列表中。
const char* list = "ABCXZ"; if (strchr(list, ch)) { // 'ch' is 'A', 'B', 'C', 'X', or 'Z' }
在这种情况下,您可以使用switch
:
switch (ch) { case 'A': case 'B': case 'C': // do something break; case 'D': case 'E': case 'F': // do something else break; ... }
虽然这比使用strchr
稍微冗长strchr
,但不涉及任何函数调用。 它也适用于C和C ++。
请注意,由于使用了逗号运算符,所以您build议的替代语法将无法如您所期望的那样工作:
if ( ch == 'A', 'B', 'C', 'D', 'E', 'F' )
首先比较'A'
,然后丢弃结果。 然后评估并丢弃'B'
,然后'C'
,等等,直到评估'F'
。 然后'F'
成为条件的值。 由于任何非零值在布尔上下文中被评估为真(并且'F'
不为零),因此上述expression式将始终为真。
模板允许我们用这种方式expression自己:
if (range("AF").contains(ch)) { ... }
它需要一个小pipe道,你可以把它放在图书馆里。
这实际上编译出来非常高效(至less在gcc和clang上)。
#include <cstdint> #include <tuple> #include <utility> #include <iostream> namespace detail { template<class T> struct range { constexpr range(T first, T last) : _begin(first), _end(last) {} constexpr T begin() const { return _begin; } constexpr T end() const { return _end; } template<class U> constexpr bool contains(const U& u) const { return _begin <= u and u <= _end; } private: T _begin; T _end; }; template<class...Ranges> struct ranges { constexpr ranges(Ranges...ranges) : _ranges(std::make_tuple(ranges...)) {} template<class U> struct range_check { template<std::size_t I> bool contains_impl(std::integral_constant<std::size_t, I>, const U& u, const std::tuple<Ranges...>& ranges) const { return std::get<I>(ranges).contains(u) or contains_impl(std::integral_constant<std::size_t, I+1>(),u, ranges); } bool contains_impl(std::integral_constant<std::size_t, sizeof...(Ranges)>, const U& u, const std::tuple<Ranges...>& ranges) const { return false; } constexpr bool operator()(const U& u, std::tuple<Ranges...> const& ranges) const { return contains_impl(std::integral_constant<std::size_t, 0>(), u, ranges); } }; template<class U> constexpr bool contains(const U& u) const { range_check<U> check {}; return check(u, _ranges); } std::tuple<Ranges...> _ranges; }; } template<class T> constexpr auto range(T t) { return detail::range<T>(t, t); } template<class T> constexpr auto range(T from, T to) { return detail::range<T>(from, to); } // this is the little trick which turns an ascii string into // a range of characters at compile time. It's probably a bit naughty // as I am not checking syntax. You could write "ApZ" and it would be // interpreted as "AZ". constexpr auto range(const char (&s)[4]) { return range(s[0], s[2]); } template<class...Rs> constexpr auto ranges(Rs...rs) { return detail::ranges<Rs...>(rs...); } int main() { std::cout << range(1,7).contains(5) << std::endl; std::cout << range("af").contains('b') << std::endl; auto az = ranges(range('a'), range('z')); std::cout << az.contains('a') << std::endl; std::cout << az.contains('z') << std::endl; std::cout << az.contains('p') << std::endl; auto rs = ranges(range("af"), range("pz")); for (char ch = 'a' ; ch <= 'z' ; ++ch) { std::cout << ch << rs.contains(ch) << " "; } std::cout << std::endl; return 0; }
预期产出:
1 1 1 1 0 a1 b1 c1 d1 e1 f1 g0 h0 i0 j0 k0 l0 m0 n0 o0 p1 q1 r1 s1 t1 u1 v1 w1 x1 y1 z1
作为参考,这里是我的原始答案:
template<class X, class Y> bool in(X const& x, Y const& y) { return x == y; } template<class X, class Y, class...Rest> bool in(X const& x, Y const& y, Rest const&...rest) { return in(x, y) or in(x, rest...); } int main() { int ch = 6; std::cout << in(ch, 1,2,3,4,5,6,7) << std::endl; std::string foo = "foo"; std::cout << in(foo, "bar", "foo", "baz") << std::endl; std::cout << in(foo, "bar", "baz") << std::endl; }
如果你需要检查一个字符对任意字符集,你可以尝试写这个:
std::set<char> allowed_chars = {'A', 'B', 'C', 'D', 'E', 'G', 'Q', '7', 'z'}; if(allowed_chars.find(ch) != allowed_chars.end()) { /*...*/ }
关于这个过度回答的问题的另一个答案,我只是为了完整而包括在内。 在这里的所有答案之间,你应该find适用于你的应用程序的东西 。
所以另一种select是查找表:
// On initialization: bool isAcceptable[256] = { false }; isAcceptable[(unsigned char)'A'] = true; isAcceptable[(unsigned char)'B'] = true; isAcceptable[(unsigned char)'C'] = true; // When you want to check: char c = ...; if (isAcceptable[(unsigned char)c]) { // it's 'A', 'B', or 'C'. }
如果你愿意的话,嘲笑C风格的静态types,但是他们确实完成了工作。 我想你可以使用std::vector<bool>
如果数组在晚上让你继续。 你也可以使用bool
之外的types。 但是你明白了。
显然,这对于例如wchar_t
变得麻烦,并且对于多字节编码实际上是不可用的。 但是对于你的char
示例,或者任何可以用于查找表的东西,它都可以。 因人而异。
与C strchr
答案类似,在C ++中,您可以构造一个string并根据其内容检查字符:
#include <string> ... std::string("ABCDEFGIKZ").find(c) != std::string::npos;
对于'F'
和'Z'
,以上将返回true
,而对于'z'
或'O'
则为false
。 这段代码并不假定字符的连续表示。
这是std::string::npos
,因为std::string::find
std::string::npos
时会返回std::string::npos
。
住在Coliru
编辑:
还有另外一个C ++方法, 它不涉及dynamic分配,但涉及更长的一段代码:
#include <algorithm> // std::find #include <iterator> // std::begin and std::end ... char const chars[] = "ABCDEFGIKZ"; return std::find(std::begin(chars), std::end(chars), c) != std::end(chars);
这与第一个代码片段类似: std::find
search给定的范围,并返回一个特定的值,如果没有find该项目。 这里所说的具体值就是范围的结尾。
住在Coliru
一个选项是unordered_set
。 将感兴趣的字符放入集合中。 然后只要检查有问题的字符数:
#include <iostream> #include <unordered_set> using namespace std; int main() { unordered_set<char> characters; characters.insert('A'); characters.insert('B'); characters.insert('C'); // ... if (characters.count('A')) { cout << "found" << endl; } else { cout << "not found" << endl; } return 0; }
有问题的解决scheme,而不是语言,但在编码实践 – 重构 。
我确信读者会发现这个答案非常不正规,但是 – 重构可以并经常用来隐藏方法调用背后的一块混乱的代码。 该方法可以稍后清理,也可以保留原样。
您可以创build以下方法:
private bool characterIsValid(char ch) { return (ch == 'A' || ch == 'B' || ch == 'C' || ..... ); }
然后这个方法可以简写为:
if (characterIsValid(ch)) ...
用这么多的检查重新使用这个方法,只在任何地方返回一个布尔值。
对于这个特定的情况,你可以使用char
是一个整数并testing范围的事实:
if(ch >= 'A' && ch <= 'C') { ... }
但总的来说,这是不可能的。 如果你想压缩你的代码,只需使用布尔函数
if(compare_char(ch)) { ... }
绝大多数现代系统的XY答案是不用麻烦的。
您可以利用这样一个事实,即今天使用的每个字符编码实际上都将字母表存储在一个按顺序排列的连续块中。 A后面是B,B后面是C,等等…到Z.这使您可以对字母进行简单的math技巧来将字母转换为数字。 例如字母C减去字母A,“C” – “A”,是2,c和a之间的距离。
在上述评论中讨论了一些字符集EBCDIC,因为这里没有讨论的范围,所以不是连续的或者是连续的。 他们是罕见的,但偶尔你会find一个。 当你这样做的时候…那么,这里的其他答案大多提供了合适的解决scheme。
我们可以用一个简单的数组来将字母值映射为字母:
// a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p, q,r,s,t,u,v,w,x,y, z int lettervalues[] = {1,3,3,2,1,4,2,4,1,8,5,1,3,1,1,3,10,1,1,1,1,4,4,8,4,10};
所以'c' - 'a'
是2, lettervalues[2]
会导致3,C的字母值。
没有,如果陈述或条件逻辑需要什么。 所有你需要做的debugging是certificate读lettervalues
,以确保你input了正确的值。
当你用C ++进行更多的学习时,你会发现lettervalues
应该是static
(current translation unit-only access)和const
(不能被改变),可能是constexpr
(不能在编译时修改和修改)。 如果你不知道我在说什么,别担心。 之后你会覆盖全部三个。 如果没有,谷歌他们。 所有都是非常有用的工具。
使用这个数组可以像这样简单
int ComputeWordScore(std::string in) { int score = 0; for (char ch: in) // for all characters in string { score += lettervalues[ch - 'a']; } return score; }
但是这有两个致命的盲点:
首先是大写字母。 对不起Ayn Rand,但'A'不是'a', 'A'-'a'
不是零。 这可以通过使用std::tolower
或std::toupper
将所有input转换为已知的情况来解决。
int ComputeWordScore(std::string in) { int score = 0; for (char ch: in) // for all characters in string { score += lettervalues[std::tolower(ch) - 'a']; } return score; }
另一个是input不是字母的字符。 例如,“1”。 'a' - '1'
将导致不在数组中的数组索引。 这不好。 如果你幸运的话,你的程序将会崩溃,但是任何事情都可能发生,包括看起来你的程序工作。 详细了解未定义的行为 。
幸运的是,这也有一个简单的解决方法:只计算良好input的分数。 您可以使用std::isalpha
来testing有效的字母字符。
int ComputeWordScore(std::string in) { int score = 0; for (char ch: in) // for all characters in string { if (std::isalpha(ch)) { score += lettervalues[std::tolower(ch) - 'a']; } else { // do something that makes sense here. } } return score; }
我的东西会return -1;
。 -1是不可能的单词分数,所以任何调用ComputeWordScore
人都可以testing-1并拒绝用户的input。 他们用它做的不是ComputeWordScore
的问题。 一般来说,你可以做一个function更好的愚蠢,错误应该由最接近的一块代码来处理,这些代码具有做出决定所需的所有信息。 在这种情况下,无论在string中读取什么内容,都可能需要决定如何处理不良string,并且ComputeWordScore
可以继续计算字词分数。
为了一个简单而有效的解决scheme,你可以使用memchr()
:
#include <string.h> const char list[] = "ABCXZ"; if (memchr(list, ch, sizeof(list) - 1)) { // 'ch' is 'A', 'B', 'C', 'X', or 'Z' }
请注意,对于此任务, memchr()
比strchr()
更适合,因为strchr()
会在string末尾find空字符'\0'
,这在大多数情况下是不正确的。
如果列表是dynamic的或外部的,并且它的长度没有提供,那么strchr()
方法会更好,但是您应该检查ch
是否与0
不同,因为strchr()
会在string的末尾find它:
#include <string.h> extern char list[]; if (ch && strchr(list, ch)) { // 'ch' is one of the characters in the list }
另一个效率更高但更简洁的C99特定解决scheme使用一个数组:
#include <limits.h> const char list[UCHAR_MAX + 1] = { ['A'] = 1, ['B'] = 1, ['C'] = 1, ['X'] = 1, ['Z'] = 1 }; if (list[(unsigned char)ch]) { /* ch is one of the matching characters */ }
但是请注意,上述所有解决scheme都假设为char
types。 如果ch
有不同的types,他们会接受误报。 这是如何解决这个问题的:
#include <string.h> extern char list[]; if (ch == (char)ch && ch && strchr(list, ch)) { // 'ch' is one of the characters in the list }
此外,如果您正在比较unsigned char
值,请谨防陷阱:
unsigned char ch = 0xFF; if (ch == '\xFF') { /* test fails if `char` is signed by default */ } if (memchr("\xFF", ch, 1)) { /* test succeeds in all cases, is this OK? */ }
大部分的简要版本已经被覆盖了,所以我会用一些辅助macros来覆盖优化的例子,使它们更简洁一些。
恰恰相反,如果你的范围落在你每秒长度的位数内,你可以使用一个位掩码来组合所有的常量,并且检查你的值是否在范围内,并且当按位进行时,variables的位掩码是非零的不变的位掩码。
/* This macro assumes the bits will fit in a long integer type, * if it needs to be larger (64 bits on x32 etc...), * you can change the shifted 1ULs to 1ULL or if range is > 64 bits, * split it into multiple ranges or use SIMD * It also assumes that a0 is the lowest and a9 is the highest, * You may want to add compile time assert that: * a9 (the highest value) - a0 (the lowest value) < max_bits * and that a1-a8 fall within a0 to a9 */ #define RANGE_TO_BITMASK_10(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9) \ (1 | (1UL<<((a1)-(a0))) | (1UL<<((a2)-(a0))) | (1UL<<((a3)-(a0))) | \ (1UL<<((a4)-(a0))) | (1UL<<((a5)-(a0))) | (1UL<<((a6)-(a0))) | \ (1UL<<((a7)-(a0))) | (1UL<<((a8)-(a0))) | (1UL<<((a9)-(a0))) ) /*static inline*/ bool checkx(int x){ const unsigned long bitmask = /* assume 64 bits */ RANGE_TO_BITMASK_10('A','B','C','F','G','H','c','f','y','z'); unsigned temp = (unsigned)x-'A'; return ( ( temp <= ('z'-'A') ) && !!( (1ULL<<temp) & bitmask ) ); }
由于所有#值都是常量,所以在编译时它们将被合并为1位掩码。 对于范围,1移位和1位按1和1比较,除非编译器能够进一步优化,否则就会发生叮铛声(它使用位testing指令BTQ):
checkx: # @checkx addl $-65, %edi cmpl $57, %edi ja .LBB0_1 movabsq $216172936732606695, %rax # imm = 0x3000024000000E7 btq %rdi, %rax setb %al retq .LBB0_1: xorl %eax, %eax retq
它可能看起来像C端更多的代码,但如果你正在寻找优化,这看起来可能是在组装方面值得。 我敢肯定,有人可以在macros观上有创造力,使其在真正的编程环境中比这个“概念validation”更有用。
作为一个macros,这会变得有点复杂,所以这里有一组macros来设置一个C99查找表。
#include <limits.h> #define INIT_1(v,a) [ a ] = v #define INIT_2(v,a,...) [ a ] = v, INIT_1(v, __VA_ARGS__) #define INIT_3(v,a,...) [ a ] = v, INIT_2(v, __VA_ARGS__) #define INIT_4(v,a,...) [ a ] = v, INIT_3(v, __VA_ARGS__) #define INIT_5(v,a,...) [ a ] = v, INIT_4(v, __VA_ARGS__) #define INIT_6(v,a,...) [ a ] = v, INIT_5(v, __VA_ARGS__) #define INIT_7(v,a,...) [ a ] = v, INIT_6(v, __VA_ARGS__) #define INIT_8(v,a,...) [ a ] = v, INIT_7(v, __VA_ARGS__) #define INIT_9(v,a,...) [ a ] = v, INIT_8(v, __VA_ARGS__) #define INIT_10(v,a,...) [ a ] = v, INIT_9(v, __VA_ARGS__) #define ISANY10(x,...) ((const unsigned char[UCHAR_MAX+1]){ \ INIT_10(-1, __VA_ARGS__) \ })[x] bool checkX(int x){ return ISANY10(x,'A','B','C','F','G','H','c','f','y','z'); }
这个方法将使用一个(通常)256字节的表和一个在gcc中简化如下的查找:
checkX: movslq %edi, %rdi # x, x cmpb $0, C.2.1300(%rdi) #, C.2 setne %al #, tmp93 ret
注意:在这个方法的查找表中,Clang并不是很好,因为它在每个函数调用中都设置了堆栈函数内部的const表,所以你需要使用INIT_10初始化一个static const unsigned char [UCHAR_MAX+1]
以外的函数来实现对gcc的类似优化。