只是代替:
if ( ch == 'A' || ch == 'B' || ch == 'C' || .....
例如,要像这样做:
if ( ch == 'A', 'B', 'C', ...
是否有更简洁的方式来总结条件吗?
只是代替:
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'
}
ch = '\0'
也会匹配。如果你已经知道ch
不能为null,那么没问题,否则这是一个bug。你可以用以下代码避免这个问题:if (memchr("ABCXY", ch, 5) { ... }
,这样做应该更快。 - chqrlieswitch
: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'
是非零值),因此上述表达式始终为真。
模板可以让我们以如下的方式表达自己:
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;
}
如果您需要检查一个字符是否在任意字符集合中,您可以尝试编写以下代码:
std::set<char> allowed_chars = {'A', 'B', 'C', 'D', 'E', 'G', 'Q', '7', 'z'};
if(allowed_chars.find(ch) != allowed_chars.end()) {
/*...*/
}
static const
的set
,也许是这样? - Laurent LA RIZZAstd::set
是一种相当低效的数据结构,用于存储字符(Chandler 在 cppcon14 上阐述得很好:https://www.youtube.com/watch?v=fHNmRkzxHWs)。如果您有时间,您可能需要查看我的伪基准测试:https://ideone.com/h6WxBI。在我的计算机上,`strchr`的性能比`std::set`的解决方案高出两倍或更多,但这可能高度依赖于系统、编译器和基准测试的具体情况。实际情况并不像我预期的那么糟糕。 - MikeMBbool charIsAllowed [256]
查找表,其中使用 unsigned char 可以获得 O(1) 的复杂度。 - Jason C作为这个问题的又一个回答,我只是为了完整性而包含在内。在这里的所有答案中,您应该会找到适用于您的应用程序的某些答案。
因此,另一个选择是查找表:
// 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)。
std::map
的提及。 - user4581301256
替换为 1<<CHAR_BIT
。 - Ruslanstrchr
函数类似,在 C++ 中你可以构造一个字符串并检查字符是否在其中:#include <string>
...
std::string("ABCDEFGIKZ").find(c) != std::string::npos;
std::string::find
在字符串中找不到该字符时返回std::string::npos
。
Live 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
搜索给定的范围,如果未找到该项,则返回特定值。在这里,该特定值是范围的末尾。
std::find
,这将消除动态分配的开销,但会增加代码长度。 - jaggedSpirestd::string_view
和ranges这样的东西;) - MikeMB一种选择是使用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;
}
针对您的问题,有一个解决方案不是通过语言,而是使用编码实践 - 重构。
我相信读者会认为这个答案非常不传统,但是 - 重构可以并经常用于隐藏一段混乱的代码背后的方法调用。该方法以后可以被清理,也可以保持原样。
您可以创建以下方法:
private bool characterIsValid(char ch) {
return (ch == 'A' || ch == 'B' || ch == 'C' || ..... );
}
然后可以使用较短的形式调用此方法:
if (characterIsValid(ch)) ...
可以在任何地方重复使用该方法,并进行多个检查,只返回布尔值。
private bool methodName()
这段代码既不像C语言也不像C++语言... - Ruslan对于一个简单而有效的解决方案,您可以使用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? */
}
针对这种情况,你可以利用char
是一个整数并测试其范围:
if(ch >= 'A' && ch <= 'C')
{
...
}
不幸的是,通常情况下这是不可能的。如果你想压缩你的代码,只需使用布尔函数。
if(compare_char(ch))
{
...
}
isupper(ch)
函数? - Barmar