字符串类的接口通常具有名为IsEmpty
(VCL)或empty
(STL)的方法。这是绝对合理的,因为它是一个特例,但使用这些方法的代码通常必须否定这个谓词,这会导致“光学(甚至是心理上的)开销” (感叹号不是很明显,尤其是在开括号后)。例如,参见这个(简化的)代码:
/// 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;
}
它有四个否定,因为空案例是要跳过的。在设计界面时,我经常会观察到这种否定,而且不是一个大问题,但这很烦人。我只想支持编写易于理解且易于阅读的代码。我希望你能理解我的观点。
也许我只是失明了:你怎么解决上述问题?
编辑:在阅读了一些评论之后,我认为原始代码使用VCL的System::AnsiString
类是有必要的。这个类提供了一个非常易读的IsEmpty
方法:
if (text.IsEmpty()) { /* ... */ } // read: if text is empty ...
如果没有否定:
if (!text.IsEmpty()) { /* ... */} // read: if not text is empty ...
...而不是如果文字不为空。我认为文字is
最好留给读者的幻想,让否定的工作做得好。好吧,也许不是一个普遍的问题...
答案 0 :(得分:27)
在大多数情况下,您可以颠倒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)) {
...
}
答案 1 :(得分:12)
我认为我已经消除了支持一点数学的条件:
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()
。为了理解发生了什么,也许最好是我阐述一下如何从一开始就考虑事物。我认为事物是一个二维数组:
(可以随意交换“开始空”和“结束空”,具体取决于您是否愿意按行主要或列主要顺序进行思考。)
如果您愿意,start.empty()
和end.empty()
(或它们的逻辑not
每个都充当此2D矩阵的一个维度的索引。数学涉及简单地“线性化”该寻址,因此我们得到一个长行而不是两行和两列,如下所示:
在数学术语中,这是“行*列+列”的简单问题(或者,反之亦然,取决于您是否更喜欢行主列或列主键排序)。我最初将* 2
部分表示为比特移位,并将加法表示为逐位or
(因为先前的左移,因此知道最低有效位为空)。我发现这很容易处理,但我想我可以理解其他人可能没有的地方。
我应该补充一点:虽然我已经提到了row-major与column-major,但是从两个“x.empty”值到数组中的位置的映射基本上是任意的。我们从.empty()
获得的值意味着当值不存在时我们得到0,而当它不存在时得到1。因此,从原始值到数组位置的直接映射可能是这样的:
由于我们将值线性化,因此我们对映射的方式有几个选择:
!x.empty()
的持续使用)对于那些怀疑这种效率的人来说,它实际上已经归结为(使用VC ++):
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
似乎并没有真正影响执行速度。由于表是静态的,我认为它应该存在,但是就执行速度而言,如果表初始化涉及四个单独的动态分配,或者类似的事情,那么我们可能没有那么大的胜利。
答案 2 :(得分:6)
你可以说
if (theString.size()) { .... }
这是否更具可读性是另一回事。在这里,您调用的方法的主要目的不是告诉您事物是否为空,并依赖于隐式转换为bool
。我更喜欢!s.empty()
版本。我可能会使用not
来取笑:
if (not theString.empty()) { .... }
看到!
和not
版本混淆的人之间的相关性可能会很有趣。
答案 3 :(得分:5)
我必须重构这一点,纯粹是出于肛门保留性疾病......
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
子句。
答案 4 :(得分:4)
通常最好不要使用这种复杂的条件代码。为什么不保持简单?
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;
}
答案 5 :(得分:3)
不使用否定..;)
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;
}
编辑:好的,清理了一点......
答案 6 :(得分:3)
由于没有人愿意用我的评论输入完整的答案,所以这里是:
创建简化表达式读取的局部变量:
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;
}
编译器足够智能以消除这些变量,即使它没有,它也不会比原始变量效率低(我希望它们都是变量的单个测试)。对于只能读取条件的人来说,代码现在更具有可读性:
如果已经开始或结束,那么
当然你也可以做不同的重构来进一步简化嵌套操作的数量,比如在没有开始或结束的时候单挑出去并提前纾困......
答案 7 :(得分:3)
在全球范围内,我对你的写作方式没有任何问题;它的
其他人的选择当然更清洁
提议。如果你担心!
消失(哪个
是一种合理的担心),使用更多的空白区域。
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()
逻辑上意味着“清空容器”,而不是“是容器
空的“。但是,如果你开始包装所有的功能
名字很差的标准库,你的工作已经减少了
出你的。)
答案 8 :(得分:2)
我也在与负面逻辑的心理开销斗争。
对此的一个解决方案(当无法避免时)是检查显式条件,请考虑:
if (!container.empty())
VS
if (container.empty() == false)
第二个版本更容易阅读,因为它会像你大声朗读一样流动。它还清楚地表明您正在检查错误情况。
现在,如果这对你来说仍然不够好,我的建议是创建一个瘦的包装类,继承自你正在使用的任何容器,然后为该特定检查创建自己的方法。
例如使用字符串:
class MyString : public std::string
{
public:
bool NotEmpty(void)
{
return (empty() == false);
}
};
现在变成了:
if (container.NotEmpty())...
答案 9 :(得分:2)
如果您只关心!
可以忽略的容易程度,则可以使用标准C ++替代令牌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;
}
(请参阅替代令牌标准中的[lex.digraph]
)
答案 10 :(得分:2)
您是否会认为已分配正好相反?
#include <string>
template <typename CharType>
bool assigned(const std::basic_string<CharType>& s)
{
return !s.empty();
}
std::string fmtTimeSpec(const std::string& from, const std::string& to)
{
if (assigned(from)) {
if (assigned(to)) {
return "from "+from+" to "+to;
}
return "since "+from;
}
if (assigned(to)) {
return "until "+to;
}
return std::string();
}
“测试功能”的结构改进来自众多有用的答案。特别感谢:
答案 11 :(得分:0)
为了表达“.isEmpty()”用法的相反形式,我更喜欢这样:
if (textView.getText().toString().isEmpty()){
//do the thing if textView has nothing inside as typed.
}else if (textView.getText().toString() != ""){
// do the thing if textView has something inside as typed.
}
此外,您可以使用“.equals("")”代替 Android Studio 推荐的“!=”字体。
textView.getText().toString().equals("")
答案 12 :(得分:0)
(它可能不适用于字符串,但通常适用于容器类)
纯属偶然,我找到了这个老问题的绝佳答案(强调我的问题)
<块引用>使用 any() 怎么样? [...]
在 completely unrelated post 中作为问题的答案
<块引用>How do I know if a generator is empty from the start?
对比 empty 和 any 在英语中可能很差,但在 API 设计中绝对有意义。