How Does it End()?

How Does It End()?

 

By 刘未鹏

C++的罗浮宫(http://blog.youkuaiyun.com/pongba)

 

The End() of the Story

这里要说的便是一个这样的例子,是在教

namespace MyPrj

{

  namespace detail

  {

    template<typename InputIter>

    InputIter skip(InputIter iter, InputIter end, typename InputIter::value_type c = ' ')

    {

      while(iter != end && *iter == c)++iter;

      return iter;

    }

  } // namespace detail

 

template<typename SequenceT, typename StringT>

  inline  SequenceT&

    split(SequenceT& seq, StringT s, typename StringT::value_type delim)

  {

    typename StringT::iterator end   = s.end(),

                            start  = detail::skip(s.begin(),end,delim),

                            over  = start;

 

    for(;start != end;++over)

    {

      if(*over == delim || over == end)

      {

        seq.push_back(StringT(start,over));

        start = over = detail::skip(over,end,delim);

      }

}

    return seq;

  }

} // namespace MyProj

上面这个小小的函数看上去很简单:首先我们跳过所有可能存在的前导分隔符(分隔符一般为空格),然后进入一个循环,over迭代器每次后移一格,一旦发现等于分隔符的字符或者发现到了串的结尾(显然,结尾总是算作分隔符的),就把卡在[start,over)区间内的串拷贝到目标容器内(注意,start用于记住上次拷贝出的区间往后的第一个非分隔符的地方),并同时把startover后移(skip)到下一个非分隔符处以便进行下一轮的卡位-拷贝。循环结束条件为start == end(注意,决不能是over == end,想想为什么),也就是说如果在某一次拷贝完毕,将startover往后skip掉所有分隔符之后,我们发现已经到了串末尾,这时就代表不用接着循环了。

很直观的逻辑是不是?然而编程跟数学的区别之一就在于编程随所用语言的不同会需要程序员去注意各种各样的细节。所以以上这寥寥数行代码中存在着两处非常隐诲的错误,你能直接看出来吗?(至少我没有We End() Up with a big Assertion!

使用在使用To That End(), …

实际上直接引发我们的测试崩溃的那个于是交换两者顺序,编译,执行。哗啦~再次牺牲。为什么呢?稍稍跟踪一下便会发现,问题出在循环结束条件的摆放位置上。看起来放在循环继续条件中的

    for(;;++over)

    {

      if(over == end || *over == delim)

      {

        seq.push_back(StringT(start,over));

        start = over = detail::skip(over,end,delim);

      }

      if(start == end)

        break;

}

 

然而这两者实际上却是有着很微妙但实质性的区别的,看一下它们的逻辑图示便清楚了:

原先的逻辑的示意图

 

修改后的逻辑示意图

 

这两者的拓扑结构是不一样的。前者的over!=end检查位于区间提取操作之前,完了之后进行可能的区间提取,start/over后移,并接着直接++over。这种安排下,如果中间进行了区间提取并且将start/over后移到了串末尾,接下来的++over便会使over超过end(),越界!而第二种拓扑就不一样了,over!=end位于区间提取、start/over后移之后,这样就能及时发现start/over后移到串末尾的情况,从而阻止下面的++over操作越界。

 

此外,实际上”if(start == end) break;”也可以放在前面那个if的里面,像这样:

 

    for(;;++over)

    {

      if(over == end || *over == delim)

      {

        seq.push_back(StringT(start,over));

        start = over = detail::skip(over,end,delim);

        if(start == end)

          break;

      }

}

 

这是因为导致start==end的只可能是由于start被后移了,而导致start后移的只可能是由于外围的if成立。这样修改了之后,就免除了每重循环都去检查if(start == end)的额外开销。虽然对于这个小函数来说根本无关痛痒,然而节俭是美德:)说不定遇到什么场合下这类节省可以省下可观开销呢。

 

最后,由于循环结束检查被放到了循环体中部,所以一开始就是直接进入循环的了。然而,如果一开始循环就不该进入呢?比如说,一个空串,这时候start/over一开始就等于end,而按照上面的逻辑,循环仍然进入,if(over == end || *over == delim)仍然满足,结果往seqpush了一个空串。然后,才是if(start == end)check,从而退出循环。那个pushseq的空串虽然无伤大雅,然而并不符合算法向外界提供的保证,所以最好还是在进入循环之前检查一下start是否等于end,是就立即让函数返回吧。

 

The End()

说实话这种看似简单直观的基础编程题其实还是挺锻炼基本编程素养的,毕竟,墙也是一块块砖垒起来的。不过,实际编程中这类情况还真罕见,即便有也可以用其它方案绕过去。如:

    while(start!=end)

    {

      over = std::find(over,end,delim);

      seq.push_back(StringT(start,over));

      start = over = detail::skip(over,end,delim);

}

 

以上做法由于将寻找下一个分隔符“skip连续的分隔符这两个逻辑分离了开来(前面的实现是把“skip连续的分隔符套在了寻找分隔符逻辑的内部了,耦合在一起,所以弄得逻辑异常复杂。所以还是谨遵爱因斯坦的名言吧:)别乱耍花枪。

 

不过话说回来(类)End()-less

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值