是否有更短的方式来写复合词'如果'条件?

时间:2016-08-25 16:57:42

标签: c++ c if-statement

只需代替

if  ( ch == 'A' || ch == 'B' || ch == 'C' || .....

例如,要执行喜欢

if  ( ch == 'A', 'B', 'C', ...

是否还有一种更短的方式来总结条件?

12 个答案:

答案 0 :(得分:85)

strchr()可用于查看角色是否在列表中。

const char* list = "ABCXZ";
if (strchr(list, ch)) {
  // 'ch' is 'A', 'B', 'C', 'X', or 'Z'
}

答案 1 :(得分:39)

在这种情况下,您可以使用switch

switch (ch) {
case 'A':
case 'B':
case 'C':
    // do something
    break;
case 'D':
case 'E':
case 'F':
    // do something else
    break;
...
}

虽然这比使用strchr稍微冗长,但它不涉及任何函数调用。它也适用于C和C ++。

请注意,由于使用了逗号运算符,您建议的替代语法将无法正常工作:

if  ( ch == 'A', 'B', 'C', 'D', 'E', 'F' )

首先将ch'A'进行比较,然后丢弃结果。然后评估并丢弃'B',然后'C',等等,直到评估'F'。然后'F'成为条件的值。由于在布尔上下文中评估为true的任何非零值(并且'F'非零),因此上述表达式将始终为true。

答案 2 :(得分:34)

模板允许我们以这种方式表达自己:

if (range("A-F").contains(ch)) { ... }

它需要一些管道,你可以把它放在一个库中。

这实际上编译得非常有效(至少在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 "A-Z".
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("a-f").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("a-f"), range("p-z"));
    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;
}

答案 3 :(得分:15)

如果您需要针对任意一组字符检查字符,可以尝试编写:

std::set<char> allowed_chars = {'A', 'B', 'C', 'D', 'E', 'G', 'Q', '7', 'z'};
if(allowed_chars.find(ch) != allowed_chars.end()) {
    /*...*/
}

答案 4 :(得分:14)

关于这个过度回答的问题的又一个答案,我只是为了完整性而包括在内。在这里的所有答案之间,您应该找到适用于您的应用程序的某些

所以另一个选项是查找表:

// 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风格的静态演员表,但他们确实完成了工作。我想你可以使用std::vector<bool>如果数组让你夜不能寐。您还可以使用bool以外的类型。但是你明白了。

显然这对于​​例如wchar_t,几乎无法使用多字节编码。但是对于你的char示例,或任何适合查找表的内容,它都会这样做。 YMMV。

答案 5 :(得分:12)

与C struct LinkedList答案类似,在C ++中,您可以构造一个字符串并根据其内容检查字符:

strchr

上述内容将为#include <string> ... std::string("ABCDEFGIKZ").find(c) != std::string::npos; true返回'F''Z'false返回'z'。此代码不假设字符的连续表示。

这是有效的,因为std::string::find在找不到字符串中的字符时会返回'O'

Live on Coliru

编辑:

还有另一种C ++方法涉及动态分配,但确实需要更长的代码:

std::string::npos

这与第一个代码段的工作方式类似:std::find搜索给定范围,如果找不到该项,则返回特定值。这里,所述特定值是范围的结束。

Live on Coliru

答案 6 :(得分:6)

一个选项是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;
}

答案 7 :(得分:6)

您的问题有解决方案,不是语言,而是编码实践 - 重构

我非常肯定读者会发现这个答案非常不正统,但是 - 重构可以并且经常被用来隐藏方法调用背后的混乱代码。该方法可以在以后清理,也可以保留原样。

您可以创建以下方法:

private bool characterIsValid(char ch) {
    return (ch == 'A' || ch == 'B' || ch == 'C' || ..... );
}

然后可以用简短形式调用此方法:

if (characterIsValid(ch)) ...

使用如此多的检查重用该方法,并且只在任何地方返回布尔值。

答案 8 :(得分:2)

对于这种特定情况,您可以使用char是一个整数并测试范围的事实:

if(ch >= 'A' && ch <= 'C')
{
    ...
}

但总的来说,不幸的是,这是不可能的。如果你想压缩代码,只需使用布尔函数

if(compare_char(ch))
{
    ...
}

答案 9 :(得分:2)

绝大多数现代系统中的X-Y answer都不会烦恼。

您可以利用以下事实:现在使用的每个字符编码都将字母表存储在一个按顺序排列的连续块中。 A后面是B,B后面跟着C ......等到Z.这允许你在字母上做简单的数学技巧,把字母转换成数字。例如,字母C减去字母A,&#39; C&#39; - &#39; A&#39;,是2,c和a之间的距离。

在上面的评论中讨论了一些字符集,EBCDIC,由于这里的讨论范围不合适,因此不是连续的或连续的。它们很少见,但偶尔你会找到一个。当你这么做的时候......这里的大多数其他答案都提供了合适的解决方案。

我们可以使用它来将字母值映射到带有简单数组的字母:

//                    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的字母值。

如果语句或条件逻辑需要什么,那就没有。您需要进行的所有调试都是校对lettervalues,以确保输入正确的值。

当您在C ++中学习更多内容时,您将了解到lettervalues应为static(当前仅限翻译单元访问权限)和const(无法更改),可能{{1 (在编译时无法更改和修复)。如果你不知道我在说什么,请不要担心。你以后会覆盖这三个。如果没有,谷歌他们。所有这些都是非常有用的工具。

使用此数组可以像

一样简单
constexpr

但这有两个致命的盲点:

首先是大写字母。对不起艾恩兰德,但是&#39; A&#39;不是&#39;&#39;和int ComputeWordScore(std::string in) { int score = 0; for (char ch: in) // for all characters in string { score += lettervalues[ch - 'a']; } return score; } 不是零。这可以通过使用std::tolowerstd::toupper将所有输入转换为已知案例来解决。

'A'-'a'

另一个是输入不是字母的字符。例如,&#39; 1&#39;。 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; } 将导致数组中没有数组索引。这是不好的。如果你很幸运,你的程序会崩溃,但任何事情都可能发生,包括看起来好像你的程序有效。阅读Undefined Behaviour了解更多信息。

幸运的是,这也有一个简单的解决方法:只计算得分以获得良好的输入。您可以使用std::isalpha测试有效的字母字符。

'a' - '1'

我的其他内容将是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; } 。 -1是一个不可能的单词分数,所以任何调用return -1;的人都可以测试-1并拒绝用户的输入。他们用它做什么不是ComputeWordScore的问题。通常情况下,你可以创建一个函数,更好,并且错误应该由最接近的代码处理,这些代码具有做出决策所需的所有信息。在这种情况下,无论在字符串中读取什么,都可能会决定如何处理坏字符串,而ComputeWordScore可以继续计算单词分数。

答案 10 :(得分:2)

要获得简单有效的解决方案,您可以使用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()会在字符串末尾找到空字符'\0',这对大多数人来说都是错误的例。

如果列表是动态列表或外部列表并且未提供其长度,则strchr()方法会更好,但您应该检查ch0是否不同strchr()会在字符串的末尾找到它:

#include <string.h>

extern char list[];
if (ch && strchr(list, ch)) {
    // 'ch' is one of the characters in the list
}

另一个更有效但更简洁的 C99 特定解决方案使用数组:

#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 */
}

但请注意,上述所有解决方案均假设ch具有char类型。如果ch具有不同的类型,则会接受误报。以下是解决此问题的方法:

#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? */
}

答案 11 :(得分:1)

大多数简洁版已经被覆盖,因此我将使用一些辅助宏来覆盖优化的案例,以使它们更加简洁。

如果您的范围落在每长度的位数内,您可以使用位掩码组合所有常量,并检查您的值是否在范围内,并且当按位时变量的位掩码为非零 - 使用常量位掩码。

/* 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个按位和...除非编译器可以进一步优化,结果是clang can(它使用位测试指令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端看起来可能看起来更像代码,但是如果你想要优化,看起来它在组装方面可能是值得的。我确信有人可以通过宏来创造它,使其在真正的编程环境中比“概念证明”更有用。

这会像宏一样复杂,所以这里有一组宏来设置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初始化{{1在函数之外实现与gcc类似的优化。