字符串类的接口通常具有名为IsEmpty
(VCL)或empty
(STL)的方法。这是绝对合理的,因为这是特例,但是使用这些方法的代码通常必须否定该谓词,从而导致“ optical(甚至心理上)overhead”(感叹号不是非常明显,尤其是在括号后面)。例如,查看此(简化)代码:
/// format an optional time specification for output
std::string fmtTime(const std::string& start, const std::string& end)
{
std::string time;
if (!start.empty() || !end.empty()) {
if (!start.empty() && !end.empty()) {
time = "from "+start+" to "+end;
} else {
if (end.empty()) {
time = "since "+start;
} else {
time = "until "+end;
}
}
}
return time;
}
它具有四个否定,因为空白的情况就是要跳过的情况。在设计接口时,我经常观察到这种否定,这是[[不是大问题,但是很烦人。我只希望支持编写易于理解的代码。希望您能理解我的观点。
也许我只是被盲目击中:您将如何解决上述问题?Edit:
阅读一些评论后,我认为有必要说原始代码使用了VCL的类System::AnsiString
。此类提供了一个IsEmpty
方法,该方法非常易读: if (text.IsEmpty()) { /* ... */ } // read: if text is empty ...
如果未否定:
if (!text.IsEmpty()) { /* ... */} // read: if not text is empty ...
...而不是如果文本不为空。我认为字面意思
is
最好放在读者的幻想中,以使否定也很好地起作用。好吧,也许不是一个普遍的问题...
if
和else
的顺序以清除代码:const std::string fmtTime(const std::string& start, const std::string& end)
{
std::string time;
if (start.empty() && end.empty()) {
return time;
}
if (start.empty() || end.empty()) {
if (end.empty()) {
time = "since "+start;
} else {
time = "until "+end;
}
} else {
time = "from "+start+" to "+end;
}
return time;
}
甚至在进行一些重构后更加干净:
std::string fmtTime(const std::string& start, const std::string& end) { if (start.empty() && end.empty()) { return std::string(); } if (start.empty()) { return "until "+end; } if (end.empty()) { return "since "+start; } return "from "+start+" to "+end; }
并且为了最终的紧凑性(尽管我更喜欢以前的版本,因为它易于阅读):
std::string fmtTime(const std::string& start, const std::string& end) { return start.empty() && end.empty() ? std::string() : start.empty() ? "until "+end : end.empty() ? "since "+start : "from "+start+" to "+end; }
另一种可能性是创建一个辅助函数:
inline bool non_empty(const std::string &str) { return !str.empty(); } if (non_empty(start) || non_empty(end)) { ... }
not
((有关替代令牌,请参阅标准中的const std::string fmtTime(const std::string& start, const std::string& end)
{
std::string time;
if (not start.empty() or not end.empty()) {
if (not start.empty() and not end.empty()) {
time = "from "+start+" to "+end;
} else {
if (end.empty()) {
time = "since "+start;
} else {
time = "until "+end;
}
}
}
return time;
}
)>>
const std::string fmtTime(const std::string& start, const std::string& end) {
typedef std::string const &s;
static const std::function<std::string(s, s)> f[] = {
[](s a, s b) { return "from " + a + " to " + b; }
[](s a, s b) { return "since " + a; },
[](s a, s b) { return "until " + b; },
[](s a, s b) { return ""; },
};
return f[start.empty() * 2 + end.empty()](start, end);
}
编辑:如果愿意,可以将数学表示为start.empty() * 2 + end.empty()
。要了解正在发生的事情,也许最好是我阐述一下我对事情的看法。我认为事物是2D数组:((随意交换“开始为空”和“结束为空”,取决于您是想按行优先还是列优先顺序进行。]
start.empty()
和end.empty()
(或它们的逻辑not
,如果愿意的话)也分别沿着该2D矩阵的一维充当索引。所涉及的数学只是简单地“线性化”了该寻址,因此,我们得到的不是一行两行,而是一行,类似于:<< img src =“ https://image.soinside.com/eyJ1cmwiOiAiaHR0cHM6Ly9pLnN0YWNrLmltZ3VyLmNvbS94U0RpcS5wbmcifQ==” alt =“在此处输入图像描述”>
用数学术语来说,这是“行*列+列”的简单问题(或者,反之亦然,取决于您是首选行优先还是列优先排序)。我最初将
* 2
部分表示为位移,并将加法表示为按位or
(由于前一次左移,知道最低有效位为空)。我觉得这很容易处理,但我想我可以理解其他人可能不知道的地方。我可能应该添加:尽管我已经提到行主列与列主列,但从两个“ x.empty”值到数组中位置的映射基本上是任意的,这一点应该很明显。从
.empty()
中获得的值表示不存在该值时将获得0,而当存在该值时将获得1。这样,从原始值到数组位置的直接映射可能像这样:<< img src =“ https://image.soinside.com/eyJ1cmwiOiAiaHR0cHM6Ly9pLnN0YWNrLmltZ3VyLmNvbS9BNjBUQS5wbmcifQ==” alt =“在此处输入图像描述”>
由于我们将值线性化,因此我们对映射的方式有几种选择:
简单地排列数组以适合我们获得的值。
- 分别反转每个维度的值(这基本上是导致原始问题的原因-不断使用
!x.empty()
)- 将两个输入合并为一个线性地址,然后通过减去3来“反转”。
mov eax, ebx
cmp QWORD PTR [rsi+16], rax
sete al
cmp QWORD PTR [rdi+16], 0
sete bl
lea eax, DWORD PTR [rbx+rax*2]
movsxd rcx, eax
shl rcx, 5
add rcx, r14
mov r9, rdi
mov r8, rsi
mov rdx, rbp
call <ridiculously long name>::operator()
即使f
的一次性构造也不像某些人想象的那么糟糕。它不涉及动态分配或该顺序上的任何内容。名称足够长,一开始看起来有点吓人,但最后,它主要是以下四个重复:
lea rax, OFFSET FLAT:??_7?$_Func_impl@U?$_Callable_obj@V<lambda_f466b26476f0b59760fb8bb0cc43dfaf>@@$0A@@std@@V?$allocator@V?$_Func_class@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@AEBV12@AEBV12@@std@@@2@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@AEBV42@AEBV42@@std@@6B@ mov QWORD PTR f$[rsp], rax
省略static const
似乎并没有真正影响执行速度。由于表是静态的,所以我认为它应该存在,但是就执行速度而言,如果表初始化涉及四个单独的动态分配或类似的东西,这不是我们可以期望的巨大胜利。
if (theString.size()) { .... }
是否更具可读性是另一回事。在这里,您正在调用一个方法的主要目的是不告诉您事物是否为空,并且依赖于隐式转换为bool
。我希望使用!s.empty()
版本。我可能会更喜欢使用not
:
not
[发现if (not theString.empty()) { .... }
和!
版本令人困惑的人们之间的相关性可能很有趣。
not
那里…干净干净。如果此处的内容难以阅读,请添加注释,而不要添加另一个std::string fmtTime( const std::string & start, const std::string & end ) {
if ( start.empty() ) {
if ( end.empty() ) return ""; // should diagnose an error here?
return "until " + end;
}
if ( end.empty() ) return "since " + start;
return "from " + start + " to " + end;
}
子句。
if
const std::string fmtTime(const std::string& start, const std::string& end)
{
if (start.empty() && end.empty())
{
return "";
}
// either start or end or both are not empty here.
std::string time;
if (start.empty())
{
time = "until "+end;
}
else if (end.empty())
{
time = "since "+start;
}
else // both are not empty
{
time = "from "+start+" to "+end;
}
return time;
}
消失(是合理的担心),请使用更多的空白。!
或尝试使用关键字if ( ! start.empty() || ! end.empty() ) ...
代替:
not
((对于大多数编辑者,if ( not start.empty() || not end.empty() ) ...
将被突出显示为关键字,这将引起更多关注。)否则,有两个助手功能:
not
这具有赋予功能的附加优势一个更好的名字。 (函数名是动词,所以template <typename Container>
bool
isEmpty( Container const& container )
{
return container.empty();
}
template <typename Container>
bool
isNotEmpty( Container const& container )
{
return !container.empty();
}
逻辑上是指“清空容器”,而不是“是容器空”。但是,如果您开始将所有功能包装在标准库名称不佳,您的工作量减少了给你。)
c.empty()
编辑:好的清理了更多...
创建简化表达式的局部变量:
const std::string fmtTime(const std::string& start, const std::string& end)
{
std::string ret;
if (start.empty() == end.empty())
{
ret = (start.empty()) ? "" : "from "+start+" to "+end;
}
else
{
ret = (start.empty()) ? "until "+end : "since "+start;
}
return ret;
}
编译器足够聪明,可以忽略那些变量,即使没有,它也不会比原始编译器有效(我希望两者都是对变量的单个测试)。对于只阅读条件的人来说,现在的代码有些[[可读:如果有开始或结束则
当然,您也可能会进行不同的重构,以进一步简化嵌套操作的数量,例如在没有开始或结束的情况下将其选出并尽早解救...
std::string fmtTime(const std::string& start, const std::string& end)
{
std::string time;
const bool hasStart = !start.empty();
const bool hasEnd = !end.empty();
if (hasStart || hasEnd) {
if (hasStart && hasEnd) {
time = "from "+start+" to "+end;
} else {
if (hasStart) {
time = "since "+start;
} else {
time = "until "+end;
}
}
}
return time;
}
vs
if (!container.empty())
第二个版本更容易阅读,因为它的流动方式就像您大声读出来一样。这也清楚表明您正在检查错误条件。
现在,如果这对于您仍然还不够好,我的建议是创建一个精简包装类,该包装类继承自您使用的任何容器,然后为该特定检查创建自己的方法。例如,使用字符串:
if (container.empty() == false)
现在变成正好:
class MyString : public std::string
{
public:
bool NotEmpty(void)
{
return (empty() == false);
}
};