有没有更短的方法来编写复合“if”条件?

34

只是代替

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

例如,要像这样做:

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

是否有更简洁的方式来总结条件吗?


7
没有,这个没有速记符号。 - Barmar
22
或许你想使用 isupper(ch) 函数? - Barmar
21
如果您的值是连续的,如所示,“if (ch >= 'A' && ch <= 'C')”或者它所在的任何位置。 - Weather Vane
7
这是C++还是C?因为在C++中有一种类似简写的方式,即使你检查的内容不是连续的也可以使用。 - jaggedSpire
9
请删除 C 标签或 C++标签中的一个,以清楚地确定您正在使用的编程语言。 - skrrgwasme
显示剩余5条评论
12个回答

85

strchr() 可以用来查找字符是否在列表中。

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

8
也许这是个人风格,但我会省略中间的“list”变量,直接将字符串作为参数传递。 - MikeMB
15
@monkeyman79,您等待几十秒钟才能看到按钮的反应,并不是因为人们编写了这样的代码。而是在实际优化时,人们没有正确地找到真正的瓶颈所在。此外,“恶劣的响应”有点过分;原帖作者甚至并没有询问与该代码相关的性能瓶颈。这是一个完全可以接受的回答。 - Jason C
3
如果字符是随机的,那就很好;但如果它们是连续的,就不好。 - phuclv
5
然而,它在连续字符方面表现完美无瑕。 - Jason C
6
这个快速而不完美的解决方案有一个问题:ch = '\0'也会匹配。如果你已经知道ch不能为null,那么没问题,否则这是一个bug。你可以用以下代码避免这个问题:if (memchr("ABCXY", ch, 5) { ... },这样做应该更快。 - chqrlie
显示剩余5条评论

40
在这种情况下,您可以使用 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'成为条件的值。由于在布尔上下文中任何非零值都被视为真(而'F'是非零值),因此上述表达式始终为真。


这为什么不是被接受的答案? - SQB
它并不是完全可用的。只有在运行时已知情况才能使用。 它也不是基于布尔值的 if 语句,而是一个查找操作。 - HopefullyHelpful

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;
}

7
这很好,但如果只需要比较字符,似乎有些过度设计。 - HolyBlackCat
37
每个人内心深处都想用LISP编写代码。 - Pete Becker
5
每个人内心深处都想磨掉他们计算机键盘上的尖括号键。 - Jason C
3
@RichardHodges你的意思是这个教训是始终要编写基本情况,还是说教训是错误不可读吗? - Caridorc
2
@hyde 谁在乎呢,真的。OP的评论情感非常明确,不需要进一步的吹毛求疵的分析。你的质疑只会带来不必要的噪音,没有任何收获。 - Jason C
显示剩余10条评论

14

如果您需要检查一个字符是否在任意字符集合中,您可以尝试编写以下代码:

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

4
一个static constset,也许是这样? - Laurent LA RIZZA
1
除非你处理的是非常非常大的字符集(超过256个),否则这必须是最慢的可能方式。 - MikeMB
4
为什么您认为这比“strchr”更快?而“strchr”版本为什么需要注释? - MikeMB
3
@Xirema: 我非常怀疑渐进复杂度在这里是否重要,而且std::set是一种相当低效的数据结构,用于存储字符(Chandler 在 cppcon14 上阐述得很好:https://www.youtube.com/watch?v=fHNmRkzxHWs)。如果您有时间,您可能需要查看我的伪基准测试:https://ideone.com/h6WxBI。在我的计算机上,`strchr`的性能比`std::set`的解决方案高出两倍或更多,但这可能高度依赖于系统、编译器和基准测试的具体情况。实际情况并不像我预期的那么糟糕。 - MikeMB
4
针对九个项目而言,O(n) 和 O(log(n)) 并不相关,特别是考虑到开销和其他预处理。如果您试图为某种原因进行性能论证,请使用一个 bool charIsAllowed [256] 查找表,其中使用 unsigned char 可以获得 O(1) 的复杂度。 - Jason C
显示剩余10条评论

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)。


2
对于那些稀疏的、多字节的情况,可能值得编辑一下,加入对 std::map 的提及。 - user4581301
1
通常情况下,应该将 256 替换为 1<<CHAR_BIT - Ruslan
1
这里有一些很好的注释。 - Jason C

12
与 C 中的 strchr 函数类似,在 C++ 中你可以构造一个字符串并检查字符是否在其中:
#include <string>
...
std::string("ABCDEFGIKZ").find(c) != std::string::npos;

上面的代码对于字符'F'和'Z'返回true,但对于'z'或'O'则返回false。此代码不假设字符具有连续的表示方式。
这是因为std::string::find在字符串中找不到该字符时返回std::string::nposLive on Coliru 编辑: 还有另一种C++方法,它不涉及动态分配,但需要更长的代码:
#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搜索给定的范围,如果未找到该项,则返回特定值。在这里,该特定值是范围的末尾。

在Coliru上实时演示


值得注意的是,C++ 版本要长得多,可能需要动态内存分配才能完成与 C 风格版本相同的操作。幸运的是,大部分 C 标准库也是 C++ 标准库的一部分。 - MikeMB
确实。在同一个静态数组上也可以使用std::find,这将消除动态分配的开销,但会增加代码长度。 - jaggedSpire
@MikeMB添加了一种有趣的冗长替代方案,无需动态分配。 - jaggedSpire
我真的很期待有一天在SO答案中可以使用像std::string_view和ranges这样的东西;) - MikeMB
1
第一种方法的缺点是每次都必须创建一个字符串对象。不幸的是,std::find解决方案生成的代码并不像strchr那样高效:https://godbolt.org/g/aRPgbU - kfsone
显示剩余3条评论

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;
}

6

针对您的问题,有一个解决方案不是通过语言,而是使用编码实践 - 重构

我相信读者会认为这个答案非常不传统,但是 - 重构可以并经常用于隐藏一段混乱的代码背后的方法调用。该方法以后可以被清理,也可以保持原样。

您可以创建以下方法:

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

然后可以使用较短的形式调用此方法:

if (characterIsValid(ch)) ...

可以在任何地方重复使用该方法,并进行多个检查,只返回布尔值。


5
private bool methodName() 这段代码既不像C语言也不像C++语言... - Ruslan
代码重构与编程语言无关。 - displayName

3

对于一个简单而有效的解决方案,您可以使用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()方法更好,但您应该检查ch是否与0不同,因为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? */
}

2

针对这种情况,你可以利用char是一个整数并测试其范围:

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

不幸的是,通常情况下这是不可能的。如果你想压缩你的代码,只需使用布尔函数。

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

3
请明确说明此代码所做的假设。它不具备可移植性。 - Pete Becker
1
如果字符范围更大且您正在使用EBCDIC而不是ASCII,则第一个测试将无法正常工作。 - PaulMcKenzie
5
如果有z/OS开发人员在阅读这个问题,那么我们将面临更大的问题。 - Jason C

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