我有四个 bool
值:
bool bValue1;
bool bValue2;
bool bValue3;
bool bValue4;
可接受的值为:
Scenario 1 | Scenario 2 | Scenario 3
bValue1: true | true | true
bValue2: true | true | false
bValue3: true | true | false
bValue4: true | false | false
因此,例如,这种情况是不可接受的:
bValue1: false
bValue2: true
bValue3: true
bValue4: true
目前,我提出了这个 if
语句来检测不良情况:
if(((bValue4 && (!bValue3 || !bValue2 || !bValue1)) ||
((bValue3 && (!bValue2 || !bValue1)) ||
(bValue2 && !bValue1) ||
(!bValue1 && !bValue2 && !bValue3 && !bValue4))
{
// There is some error
}
可以改进/简化该语句逻辑吗?
if
语句。此外,由于这些是布尔标志,您可以将每个场景建模为常量并对其进行检查。
if (!((bValue1 && bValue2 && bValue3) || (bValue1 && !bValue2 && !bValue3 && !bValue4)))
bool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;
我的目标是可读性:你只有 3 个场景,用 3 个单独的 if 处理它们:
bool valid = false;
if (bValue1 && bValue2 && bValue3 && bValue4)
valid = true; //scenario 1
else if (bValue1 && bValue2 && bValue3 && !bValue4)
valid = true; //scenario 2
else if (bValue1 && !bValue2 && !bValue3 && !bValue4)
valid = true; //scenario 3
恕我直言,易于阅读和调试。此外,您可以在处理 if
时分配一个变量 whichScenario
。
只有 3 个场景,我不会选择“如果前 3 个值是真的,我可以避免检查第四个值”这样的东西:它会让你的代码更难阅读和维护。
可能肯定不是一个优雅的解决方案,但在这种情况下是可以的:简单易读。
如果您的逻辑变得更复杂,请丢弃该代码并考虑使用更多内容来存储不同的可用场景(正如 Zladeck 所建议的那样)。
我真的很喜欢 this answer 中给出的第一个建议:易于阅读、不易出错、可维护
(几乎)题外话:
我不会在 StackOverflow 上写很多答案。非常有趣的是,上述接受的答案是迄今为止我历史上最受赞赏的答案(在我认为之前从未有超过 5-10 次投票),而实际上并不是我通常认为的“正确”方法。
但简单往往是“正确的做法”,很多人似乎都这么认为,我应该比我想得更多:)
我的目标是简单性和可读性。
bool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;
bool scenario2 = bValue1 && bValue2 && bValue3 && !bValue4;
bool scenario3 = bValue1 && !bValue2 && !bValue3 && !bValue4;
if (scenario1 || scenario2 || scenario3) {
// Do whatever.
}
确保用描述性的东西替换场景的名称以及标志的名称。如果它对您的特定问题有意义,您可以考虑以下替代方案:
bool scenario1or2 = bValue1 && bValue2 && bValue3;
bool scenario3 = bValue1 && !bValue2 && !bValue3 && !bValue4;
if (scenario1or2 || scenario3) {
// Do whatever.
}
这里重要的不是谓词逻辑。它描述了您的领域并清楚地表达了您的意图。这里的关键是给所有输入和中间变量起好名字。如果您找不到好的变量名,则可能表明您以错误的方式描述问题。
我们可以使用 Karnaugh map 并将您的场景简化为一个逻辑等式。我已将 Online Karnaugh map 求解器与 4 个变量的电路一起使用。
https://i.stack.imgur.com/wlO2Q.png
这产生:
https://i.stack.imgur.com/HNAvs.png
将 A, B, C, D
更改为 bValue1, bValue2, bValue3, bValue4
,这不过是:
bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4
因此,您的 if
语句变为:
if(!(bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4))
{
// There is some error
}
当您有许多变量和许多应该评估为真的条件时,卡诺图特别有用。
在将真实场景简化为逻辑方程式后,添加指示真实场景的相关评论是一种很好的做法。
//!(ABC + AB'C'D') (By K-Map logic)
。如果开发人员还不了解 K-Maps,那将是他学习 K-Maps 的好时机。
E
和 F
条件以及 4 个新场景时会发生什么?正确更新此 if
语句需要多长时间?代码审查如何检查它是否正常?问题不在于技术方面,而在于“业务”方面。
A
: ABC + AB'C'D' = A(BC + B'C'D')
分解(这甚至可以分解为 A(B ^ C)'(C + D')
,尽管我会小心地将其称为“简化”)。
这里真正的问题是:当另一个开发人员(甚至作者)必须在几个月后更改此代码时会发生什么。
我建议将其建模为位标志:
const int SCENARIO_1 = 0x0F; // 0b1111 if using c++14
const int SCENARIO_2 = 0x0E; // 0b1110
const int SCENARIO_3 = 0x08; // 0b1000
bool bValue1 = true;
bool bValue2 = false;
bool bValue3 = false;
bool bValue4 = false;
// boolean -> int conversion is covered by standard and produces 0/1
int scenario = bValue1 << 3 | bValue2 << 2 | bValue3 << 1 | bValue4;
bool match = scenario == SCENARIO_1 || scenario == SCENARIO_2 || scenario == SCENARIO_3;
std::cout << (match ? "ok" : "error");
如果有更多场景或更多标志,则表方法比使用标志更具可读性和可扩展性。支持新方案只需要表格中的另一行。
int scenarios[3][4] = {
{true, true, true, true},
{true, true, true, false},
{true, false, false, false},
};
int main()
{
bool bValue1 = true;
bool bValue2 = false;
bool bValue3 = true;
bool bValue4 = true;
bool match = false;
// depending on compiler, prefer std::size()/_countof instead of magic value of 4
for (int i = 0; i < 4 && !match; ++i) {
auto current = scenarios[i];
match = bValue1 == current[0] &&
bValue2 == current[1] &&
bValue3 == current[2] &&
bValue4 == current[3];
}
std::cout << (match ? "ok" : "error");
}
SCENARIO_2 = true << 3 | true << 2 | true << 1 | false;
2:避免使用 SCENARIO_X 变量,然后将所有可用场景存储在 <std::set<int>
中。添加一个场景就像 mySet.insert( true << 3 | false << 2 | true << 1 | false;
一样,对于 3 个场景来说可能有点矫枉过正,OP 接受了我在回答中建议的快速、肮脏和简单的解决方案。
std::find
?) 稍微简化一下。
scenario
值的位操作让我觉得不必要地容易出错。
我之前的答案已经是公认的答案,我在这里添加了一些我认为既可读又容易并且在这种情况下对未来修改开放的内容:
从@ZdeslavVojkovic 的回答(我觉得很好)开始,我想出了这个:
#include <iostream>
#include <set>
//using namespace std;
int GetScenarioInt(bool bValue1, bool bValue2, bool bValue3, bool bValue4)
{
return bValue1 << 3 | bValue2 << 2 | bValue3 << 1 | bValue4;
}
bool IsValidScenario(bool bValue1, bool bValue2, bool bValue3, bool bValue4)
{
std::set<int> validScenarios;
validScenarios.insert(GetScenarioInt(true, true, true, true));
validScenarios.insert(GetScenarioInt(true, true, true, false));
validScenarios.insert(GetScenarioInt(true, false, false, false));
int currentScenario = GetScenarioInt(bValue1, bValue2, bValue3, bValue4);
return validScenarios.find(currentScenario) != validScenarios.end();
}
int main()
{
std::cout << IsValidScenario(true, true, true, false) << "\n"; // expected = true;
std::cout << IsValidScenario(true, true, false, false) << "\n"; // expected = false;
return 0;
}
在工作中看到它here
好吧,这就是我通常追求的“优雅且可维护”(恕我直言)解决方案,但实际上,对于 OP 案例,我之前的“一堆 ifs”答案更适合 OP 要求,即使它既不优雅也不可维护。
我还想提交另一种方法。
我的想法是将布尔值转换为整数,然后使用可变参数模板进行比较:
unsigned bitmap_from_bools(bool b) {
return b;
}
template<typename... args>
unsigned bitmap_from_bools(bool b, args... pack) {
return (bitmap_from_bools(b) << sizeof...(pack)) | bitmap_from_bools(pack...);
}
int main() {
bool bValue1;
bool bValue2;
bool bValue3;
bool bValue4;
unsigned summary = bitmap_from_bools(bValue1, bValue2, bValue3, bValue4);
if (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u) {
//bad scenario
}
}
请注意该系统如何支持多达 32 个布尔值作为输入。将 unsigned
替换为 unsigned long long
(或 uint64_t
)将支持增加到 64 个案例。如果您不喜欢 if (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u)
,您还可以使用另一种可变参数模板方法:
bool equals_any(unsigned target, unsigned compare) {
return target == compare;
}
template<typename... args>
bool equals_any(unsigned target, unsigned compare, args... compare_pack) {
return equals_any(target, compare) ? true : equals_any(target, compare_pack...);
}
int main() {
bool bValue1;
bool bValue2;
bool bValue3;
bool bValue4;
unsigned summary = bitmap_from_bools(bValue1, bValue2, bValue3, bValue4);
if (!equals_any(summary, 0b1111u, 0b1110u, 0b1000u)) {
//bad scenario
}
}
bitmap_from_bools
或 bools_to_bitmap
?
bools_to_unsigned
。位图是一个很好的关键字;编辑。
summary!= 0b1111u &&...
。如果 b != c
,a != b || a != c
始终为真
这是一个简化版本:
if (bValue1 && (bValue2 == bValue3) && (bValue2 || !bValue4)) {
// acceptable
} else {
// not acceptable
}
注意,当然,这个解决方案比原来的解决方案更模糊,它的含义可能更难理解。
更新:评论中的 MSalters 发现了一个更简单的表达式:
if (bValue1&&(bValue2==bValue3)&&(bValue2>=bValue4)) ...
simple
确实是一个模糊的术语。许多人在这种情况下将其理解为开发人员更容易理解,而不是编译器生成代码,因此更冗长确实可以更简单。
考虑将您的表格尽可能直接地翻译到您的程序中。根据表格驱动程序,而不是用逻辑模仿它。
template<class T0>
auto is_any_of( T0 const& t0, std::initializer_list<T0> il ) {
for (auto&& x:il)
if (x==t0) return true;
return false;
}
现在
if (is_any_of(
std::make_tuple(bValue1, bValue2, bValue3, bValue4),
{
{true, true, true, true},
{true, true, true, false},
{true, false, false, false}
}
))
这尽可能直接将您的真值表编码到编译器中。
您也可以直接使用 std::any_of
:
using entry = std::array<bool, 4>;
constexpr entry acceptable[] =
{
{true, true, true, true},
{true, true, true, false},
{true, false, false, false}
};
if (std::any_of( begin(acceptable), end(acceptable), [&](auto&&x){
return entry{bValue1, bValue2, bValue3, bValue4} == x;
}) {
}
编译器可以内联代码,消除任何迭代并为您构建自己的逻辑。同时,您的代码准确地反映了您对问题的理解。
我只在这里提供我的答案,就像有人建议展示我的解决方案的评论一样。我要感谢大家的见解。
最后,我选择添加三个新的“场景”boolean
方法:
bool CChristianLifeMinistryValidationDlg::IsFirstWeekStudentItems(CChristianLifeMinistryEntry *pEntry)
{
return (INCLUDE_ITEM1(pEntry) &&
!INCLUDE_ITEM2(pEntry) &&
!INCLUDE_ITEM3(pEntry) &&
!INCLUDE_ITEM4(pEntry));
}
bool CChristianLifeMinistryValidationDlg::IsSecondWeekStudentItems(CChristianLifeMinistryEntry *pEntry)
{
return (INCLUDE_ITEM1(pEntry) &&
INCLUDE_ITEM2(pEntry) &&
INCLUDE_ITEM3(pEntry) &&
INCLUDE_ITEM4(pEntry));
}
bool CChristianLifeMinistryValidationDlg::IsOtherWeekStudentItems(CChristianLifeMinistryEntry *pEntry)
{
return (INCLUDE_ITEM1(pEntry) &&
INCLUDE_ITEM2(pEntry) &&
INCLUDE_ITEM3(pEntry) &&
!INCLUDE_ITEM4(pEntry));
}
然后我可以像这样应用我的验证程序:
if (!IsFirstWeekStudentItems(pEntry) && !IsSecondWeekStudentItems(pEntry) && !IsOtherWeekStudentItems(pEntry))
{
; Error
}
在我的实时应用程序中,4 个布尔值实际上是从 DWORD
中提取的,该 DWORD
中编码了 4 个值。
再次感谢大家。
INCLUDE_ITEM1
等,并且你们都很好。 :)
尽管 OP 的解决方案正是这样做的,但我没有看到任何回答来命名场景。
对我来说,最好将每个场景的注释封装到变量名或函数名中。您更有可能忽略评论而不是名称,如果您的逻辑在未来发生变化,您更有可能更改名称而不是评论。您不能重构评论。
如果您计划在您的函数之外(或可能想要)重用这些场景,则创建一个函数来说明其评估的内容(constexpr
/noexcept
可选但推荐):
constexpr bool IsScenario1(bool b1, bool b2, bool b3, bool b4) noexcept
{ return b1 && b2 && b3 && b4; }
constexpr bool IsScenario2(bool b1, bool b2, bool b3, bool b4) noexcept
{ return b1 && b2 && b3 && !b4; }
constexpr bool IsScenario3(bool b1, bool b2, bool b3, bool b4) noexcept
{ return b1 && !b2 && !b3 && !b4; }
如果可能,请制作这些类方法(如在 OP 的解决方案中)。如果您认为不会重用逻辑,则可以在函数内部使用变量:
const auto is_scenario_1 = bValue1 && bValue2 && bValue3 && bValue4;
const auto is_scenario_2 = bvalue1 && bvalue2 && bValue3 && !bValue4;
const auto is_scenario_3 = bValue1 && !bValue2 && !bValue3 && !bValue4;
编译器很可能会发现如果 bValue1 为假,那么所有场景都是假的。不要担心让它快速,只要正确和可读。如果您分析您的代码并发现这是一个瓶颈,因为编译器在 -O2 或更高时生成了次优代码,然后尝试重写它。
AC/C++方式
bool scenario[3][4] = {{true, true, true, true},
{true, true, true, false},
{true, false, false, false}};
bool CheckScenario(bool bValue1, bool bValue2, bool bValue3, bool bValue4)
{
bool temp[] = {bValue1, bValue2, bValue3, bValue4};
for(int i = 0 ; i < sizeof(scenario) / sizeof(scenario[0]); i++)
{
if(memcmp(temp, scenario[i], sizeof(temp)) == 0)
return true;
}
return false;
}
这种方法是可扩展的,就像有效条件的数量增加一样,您只需将更多的条件添加到场景列表中即可。
true
使用单个二进制表示。使用“任何非零都为真”的编译器会导致此代码失败。请注意,true
必须转换 为 1
,它只是不需要像这样存储。
2 is not equal to true but evaluates to true
,我的代码不会强制 int 1 = true
并且只要所有 true 都转换为相同的 int 值就可以工作,所以这是我的问题:为什么编译器在将 true 转换为底层 int 时应该随机执行,您能详细说明一下吗?
memcmp
来测试布尔条件是不是 C++ 方式,我也相当怀疑它是否是已建立的 C 方式。
很容易注意到前两个场景是相似的——它们共享大部分条件。如果你想选择你目前所处的场景,你可以这样写(这是修改后的@gian-paolo的解决方案):
bool valid = false;
if(bValue1 && bValue2 && bValue3)
{
if (bValue4)
valid = true; //scenario 1
else if (!bValue4)
valid = true; //scenario 2
}
else if (bValue1 && !bValue2 && !bValue3 && !bValue4)
valid = true; //scenario 3
更进一步,您会注意到,第一个布尔值必须始终为真,这是一个入口条件,因此您最终可以得到:
bool valid = false;
if(bValue1)
{
if(bValue2 && bValue3)
{
if (bValue4)
valid = true; //scenario 1
else if (!bValue4)
valid = true; //scenario 2
}
else if (!bValue2 && !bValue3 && !bValue4)
valid = true; //scenario 3
}
更重要的是,您现在可以清楚地看到,bValue2 和 bValue3 在某种程度上是相互关联的——您可以将它们的状态提取到一些具有更合适名称的外部函数或变量中(尽管这并不总是容易或合适的):
bool valid = false;
if(bValue1)
{
bool bValue1and2 = bValue1 && bValue2;
bool notBValue1and2 = !bValue2 && !bValue3;
if(bValue1and2)
{
if (bValue4)
valid = true; //scenario 1
else if (!bValue4)
valid = true; //scenario 2
}
else if (notBValue1and2 && !bValue4)
valid = true; //scenario 3
}
这样做有一些优点和缺点:
条件更小,所以更容易推理它们,
更容易进行漂亮的重命名以使这些条件更易于理解,
但是,他们需要了解范围,
而且它更僵硬
如果您预测上述逻辑会发生变化,则应使用 @gian-paolo 中提出的更直接的方法。
否则,如果这些条件已经确立,并且是一种永远不会改变的“可靠规则”,请考虑我的最后一个代码片段。
根据 mch 的建议,您可以这样做:
if(!((bValue1 && bValue2 && bValue3) ||
(bValue1 && !bValue2 && !bValue3 && !bValue4))
)
其中第一行涵盖了前两个好的案例,第二行涵盖了最后一个。
Live Demo,我在那里玩过,它通过了你的案例。
@GianPaolo 的好答案略有不同,有些人可能会觉得更容易阅读:
bool any_of_three_scenarios(bool v1, bool v2, bool v3, bool v4)
{
return (v1 && v2 && v3 && v4) // scenario 1
|| (v1 && v2 && v3 && !v4) // scenario 2
|| (v1 && !v2 && !v3 && !v4); // scenario 3
}
if (any_of_three_scenarios(bValue1,bValue2,bValue3,bValue4))
{
// ...
}
每个答案都过于复杂且难以阅读。对此的最佳解决方案是 switch()
语句。它既可读又使添加/修改其他案例变得简单。编译器也擅长优化 switch()
语句。
switch( (bValue4 << 3) | (bValue3 << 2) | (bValue2 << 1) | (bValue1) )
{
case 0b1111:
// scenario 1
break;
case 0b0111:
// scenario 2
break;
case 0b0001:
// scenario 3
break;
default:
// fault condition
break;
}
您当然可以在 case
语句中使用常量并将它们组合在一起以提高可读性。
为了清楚起见,我还会使用快捷方式变量。如前所述,场景 1 等于场景 2,因为 bValue4 的值不会影响这两个场景的真实性。
bool MAJORLY_TRUE=bValue1 && bValue2 && bValue3
bool MAJORLY_FALSE=!(bValue2 || bValue3 || bValue4)
然后你的表情变得:
if (MAJORLY_TRUE || (bValue1 && MAJORLY_FALSE))
{
// do something
}
else
{
// There is some error
}
为 MAJORTRUE 和 MAJORFALSE 变量(以及实际上为 bValue* 变量)赋予有意义的名称将有助于提高可读性和维护性。
关注问题的可读性,而不是特定的“if”语句。
虽然这会产生更多的代码行,但有些人可能认为它过分或不必要。我建议从特定的布尔值中抽象出你的场景是保持可读性的最佳方式。
通过将事物拆分为具有可理解名称的类(随意使用函数或您喜欢的任何其他工具)——我们可以更轻松地展示每个场景背后的含义。更重要的是,在具有许多移动部件的系统中 - 更容易维护和加入现有系统(同样,尽管涉及多少额外代码)。
#include <iostream>
#include <vector>
using namespace std;
// These values would likely not come from a single struct in real life
// Instead, they may be references to other booleans in other systems
struct Values
{
bool bValue1; // These would be given better names in reality
bool bValue2; // e.g. bDidTheCarCatchFire
bool bValue3; // and bDidTheWindshieldFallOff
bool bValue4;
};
class Scenario
{
public:
Scenario(Values& values)
: mValues(values) {}
virtual operator bool() = 0;
protected:
Values& mValues;
};
// Names as examples of things that describe your "scenarios" more effectively
class Scenario1_TheCarWasNotDamagedAtAll : public Scenario
{
public:
Scenario1_TheCarWasNotDamagedAtAll(Values& values) : Scenario(values) {}
virtual operator bool()
{
return mValues.bValue1
&& mValues.bValue2
&& mValues.bValue3
&& mValues.bValue4;
}
};
class Scenario2_TheCarBreaksDownButDidntGoOnFire : public Scenario
{
public:
Scenario2_TheCarBreaksDownButDidntGoOnFire(Values& values) : Scenario(values) {}
virtual operator bool()
{
return mValues.bValue1
&& mValues.bValue2
&& mValues.bValue3
&& !mValues.bValue4;
}
};
class Scenario3_TheCarWasCompletelyWreckedAndFireEverywhere : public Scenario
{
public:
Scenario3_TheCarWasCompletelyWreckedAndFireEverywhere(Values& values) : Scenario(values) {}
virtual operator bool()
{
return mValues.bValue1
&& !mValues.bValue2
&& !mValues.bValue3
&& !mValues.bValue4;
}
};
Scenario* findMatchingScenario(std::vector<Scenario*>& scenarios)
{
for(std::vector<Scenario*>::iterator it = scenarios.begin(); it != scenarios.end(); it++)
{
if (**it)
{
return *it;
}
}
return NULL;
}
int main() {
Values values = {true, true, true, true};
std::vector<Scenario*> scenarios = {
new Scenario1_TheCarWasNotDamagedAtAll(values),
new Scenario2_TheCarBreaksDownButDidntGoOnFire(values),
new Scenario3_TheCarWasCompletelyWreckedAndFireEverywhere(values)
};
Scenario* matchingScenario = findMatchingScenario(scenarios);
if(matchingScenario)
{
std::cout << matchingScenario << " was a match" << std::endl;
}
else
{
std::cout << "No match" << std::endl;
}
// your code goes here
return 0;
}
这取决于它们代表什么。
例如,如果 1 是一个键,而 2 和 3 是必须同意的两个人(除非他们同意他们需要的 NOT
第三人称 - 4 - 确认)最易读的可能是:
1 &&
(
(2 && 3)
||
((!2 && !3) && !4)
)
应大众要求:
Key &&
(
(Alice && Bob)
||
((!Alice && !Bob) && !Charlie)
)
bValue
。
进行按位运算看起来非常干净易懂。
int bitwise = (bValue4 << 3) | (bValue3 << 2) | (bValue2 << 1) | (bValue1);
if (bitwise == 0b1111 || bitwise == 0b0111 || bitwise == 0b0001)
{
//satisfying condition
}
为了清楚起见,我用 a、b、c、d 表示,用 A、B、C、D 表示补语
bValue1 = a (!A)
bValue2 = b (!B)
bValue3 = c (!C)
bValue4 = d (!D)
方程
1 = abcd + abcD + aBCD
= a (bcd + bcD + BCD)
= a (bc + BCD)
= a (bcd + D (b ^C))
使用任何适合您的方程式。
If (!bValue1 || (bValue2 != bValue3) || (!bValue4 && bValue2))
{
// you have a problem
}
b1 必须始终为真
b2 必须始终等于 b3
如果 b2(和 b3)为真,则 b4 不能为假
简单的
只是个人对公认答案的偏好,但我会写:
bool valid = false;
// scenario 1
valid = valid || (bValue1 && bValue2 && bValue3 && bValue4);
// scenario 2
valid = valid || (bValue1 && bValue2 && bValue3 && !bValue4);
// scenario 3
valid = valid || (bValue1 && !bValue2 && !bValue3 && !bValue4);
首先,假设您只能修改场景检查,我将专注于可读性并将检查包装在一个函数中,这样您就可以调用 if(ScenarioA())
。
现在,假设您确实想要/需要优化它,我建议将紧密链接的布尔值转换为常量整数,并在它们上使用位运算符
public class Options {
public const bool A = 2; // 0001
public const bool B = 4; // 0010
public const bool C = 16;// 0100
public const bool D = 32;// 1000
//public const bool N = 2^n; (up to n=32)
}
...
public isScenario3(int options) {
int s3 = Options.A | Options.B | Options.C;
// for true if only s3 options are set
return options == s3;
// for true if s3 options are set
// return options & s3 == s3
}
这使得表达场景就像列出其中的一部分一样简单,允许您使用 switch 语句跳转到正确的条件,并使以前没有见过的开发人员感到困惑。 (C# RegexOptions 用这个模式设置标志,不知道有没有c++库的例子)
对于某些人来说,嵌套的 if
可能更容易阅读。这是我的版本
bool check(int bValue1, int bValue2, int bValue3, int bValue4)
{
if (bValue1)
{
if (bValue2)
{
// scenario 1-2
return bValue3;
}
else
{
// scenario 3
return !bValue3 && !bValue4;
}
}
return false;
}
bValue1
块,那么你就可以把里面的所有东西都当作你心理过程中的一个新的页面。我敢打赌,解决问题的方式可能非常个人化,甚至是文化问题。
这个问题已经给出了几个正确的答案,但我会采取不同的观点:如果代码看起来太复杂,那么有些地方不太对劲。代码将难以调试,更有可能是“一次性使用”。
在现实生活中,当我们发现这样的情况时:
Scenario 1 | Scenario 2 | Scenario 3
bValue1: true | true | true
bValue2: true | true | false
bValue3: true | true | false
bValue4: true | false | false
当四个状态通过如此精确的模式连接时,我们正在处理模型中某个“实体”的配置。
一个极端的比喻是我们将如何在模型中描述“人类”,如果我们不知道他们作为具有连接到特定自由度的组件的单一实体存在:我们将不得不描述“躯干”的独立状态, “手臂”、“腿”和“头”,这会使理解所描述的系统变得复杂。直接结果将是异常复杂的布尔表达式。
显然,降低复杂性的方法是抽象,而 C++ 中选择的工具是对象范式。
所以问题是:为什么会有这样的模式?这是什么,它代表什么?
由于我们不知道答案,我们可以求助于数学抽象:数组:我们有三个场景,现在每个场景都是一个数组。
0 1 2 3
Scenario 1: T T T T
Scenario 2: T T T F
Scenario 3: T F F F
此时您已完成初始配置。作为一个数组。例如 std::array
有一个相等运算符:
此时您的语法变为:
if( myarray == scenario1 ) {
// arrays contents are the same
}
else if ( myarray == scenario2 ) {
// arrays contents are the same
}
else if ( myarray == scenario3 ) {
// arrays contents are the same
}
else {
// not the same
}
正如 Gian Paolo 的回答一样,它简短、清晰且易于验证/调试。在这种情况下,我们将布尔表达式的细节委托给了编译器。
如果您摆脱布尔标志,您将不必担心布尔标志的无效组合。
可接受的值为: 场景 1 |情景 2 |场景 3 bValue1:真 |真实 |真 bValue2:真 |真实 |假 bValue3:真 |真实 |假 bValue4:真 |假 |错误的
你显然有三种状态(场景)。最好对其进行建模并从这些状态中导出布尔属性,而不是相反。
enum State
{
scenario1,
scenario2,
scenario3,
};
inline bool isValue1(State s)
{
// (Well, this is kind of silly. Do you really need this flag?)
return true;
}
inline bool isValue2(State s)
{
switch (s)
{
case scenario1:
case scenario2:
return true;
case scenario3:
return false;
}
}
inline bool isValue3(State s)
{
// (This is silly too. Do you really need this flag?)
return isValue2(s);
}
inline bool isValue4(State s)
{
switch (s)
{
case scenario1:
return true;
case scenario2:
case scenario3:
return false;
}
}
这肯定比 Gian Paolo's answer 中的代码更多,但根据您的情况,这可能更易于维护:
如果添加了额外的布尔属性或场景,则需要修改一组中心函数。添加属性只需要添加一个函数。如果添加场景,在 switch 语句中启用关于未处理枚举案例的编译器警告将捕获不处理该场景的属性获取器。
添加属性只需要添加一个函数。
如果添加场景,在 switch 语句中启用关于未处理枚举案例的编译器警告将捕获不处理该场景的属性获取器。
如果您需要动态修改布尔属性,则无需在任何地方重新验证它们的组合。您无需切换单个布尔标志(这可能导致标志的无效组合),而是拥有一个从一种场景转换到另一种场景的状态机。
这种方法还具有非常高效的副作用。
当您只有 3 个案例并且每个案例的逻辑都很简单时,接受的答案就很好。
但是,如果每个案例的逻辑更复杂,或者有更多案例,则更好的选择是使用 chain-of-responsibility 设计模式。
您创建一个 BaseValidator
,其中包含对 BaseValidator
的引用和对 validate
的方法,以及在引用的验证器上调用验证的方法。
class BaseValidator {
BaseValidator* nextValidator;
public:
BaseValidator() {
nextValidator = 0;
}
void link(BaseValidator validator) {
if (nextValidator) {
nextValidator->link(validator);
} else {
nextValidator = validator;
}
}
bool callLinkedValidator(bool v1, bool v2, bool v3, bool v4) {
if (nextValidator) {
return nextValidator->validate(v1, v2, v3, v4);
}
return false;
}
virtual bool validate(bool v1, bool v2, bool v3, bool v4) {
return false;
}
}
然后,您创建多个继承自 BaseValidator
的子类,用每个验证器所需的逻辑覆盖 validate
方法。
class Validator1: public BaseValidator {
public:
bool validate(bool v1, bool v2, bool v3, bool v4) {
if (v1 && v2 && v3 && v4) {
return true;
}
return nextValidator->callLinkedValidator(v1, v2, v3, v4);
}
}
然后使用它很简单,实例化每个验证器,并将每个验证器设置为其他验证器的根:
Validator1 firstValidator = new Validator1();
Validator2 secondValidator = new Validator2();
Validator3 thirdValidator = new Validator3();
firstValidator.link(secondValidator);
firstValidator.link(thirdValidator);
if (firstValidator.validate(value1, value2, value3, value4)) { ... }
本质上,每个验证案例都有自己的类,负责 (a) 确定验证是否匹配该案例,以及 (b) 如果不匹配,则将验证发送给链中的其他人。
请注意,我不熟悉 C++。我试图匹配我在网上找到的一些示例的语法,但如果这不起作用,请将其视为伪代码。我在下面还有一个完整的 Python 示例,如果愿意,可以用作基础。
class BaseValidator:
def __init__(self):
self.nextValidator = 0
def link(self, validator):
if (self.nextValidator):
self.nextValidator.link(validator)
else:
self.nextValidator = validator
def callLinkedValidator(self, v1, v2, v3, v4):
if (self.nextValidator):
return self.nextValidator.validate(v1, v2, v3, v4)
return False
def validate(self, v1, v2, v3, v4):
return False
class Validator1(BaseValidator):
def validate(self, v1, v2, v3, v4):
if (v1 and v2 and v3 and v4):
return True
return self.callLinkedValidator(v1, v2, v3, v4)
class Validator2(BaseValidator):
def validate(self, v1, v2, v3, v4):
if (v1 and v2 and v3 and not v4):
return True
return self.callLinkedValidator(v1, v2, v3, v4)
class Validator3(BaseValidator):
def validate(self, v1, v2, v3, v4):
if (v1 and not v2 and not v3 and not v4):
return True
return self.callLinkedValidator(v1, v2, v3, v4)
firstValidator = Validator1()
secondValidator = Validator2()
thirdValidator = Validator3()
firstValidator.link(secondValidator)
firstValidator.link(thirdValidator)
print(firstValidator.validate(False, False, True, False))
同样,对于您的特定示例,您可能会发现这种过度杀伤力,但如果您最终需要满足一组更复杂的案例,它会创建更清晰的代码。
if(!bValue1)
return false;
if(bValue2 != bValue3)
return false;
if(bValue3 == false && bValuer4 == true)
return false;
return true;
我的 2 美分:声明一个变量 sum
(integer
) 以便
if(bValue1)
{
sum=sum+1;
}
if(bValue2)
{
sum=sum+2;
}
if(bValue3)
{
sum=sum+4;
}
if(bValue4)
{
sum=sum+8;
}
根据您想要的条件检查 sum
,仅此而已。
通过这种方式,您可以在将来轻松添加更多条件,使其非常易于阅读。
使用位域:
unoin {
struct {
bool b1: 1;
bool b2: 1;
bool b3: 1;
bool b4: 1;
} b;
int i;
} u;
// set:
u.b.b1=true;
...
// test
if (u.i == 0x0f) {...}
if (u.i == 0x0e) {...}
if (u.i == 0x08) {...}
PS:
这对 CPPers 来说是一个很大的遗憾。但是,UB 不是我担心的,请在 http://coliru.stacked-crooked.com/a/2b556abfc28574a1 处查看。
unsigned char*
,行为将转变为实现定义,尽管我认为简单地使用类似 ((((flag4 <<1) | flag3) << 1) | flag2) << 1) | flag1
的东西可能会更有效。
valid
的初始化程序中并用||
分隔它们来进一步简化,而不是在单独的语句块中改变valid
。我无法在评论中举例,但您可以将||
运算符沿左侧垂直对齐以使这一点非常清楚;各个条件已根据需要用括号括起来(对于if
),因此您无需在表达式中添加任何已存在的字符。if($bValue1)
中,因为这总是正确的,从技术上讲允许一些小的性能改进(尽管我们在这里谈论的数量可以忽略不计)。bValue4