StringTok教程

等这个主要魅力人物离去后,人群也散开了,Guru和我重新开始代码回顾。我小心翼翼地将鲍勃的咖啡转移到一个安全一点的地方。一切都还顺利,直到我们碰到一个使用了strok的解析函数。Guru斜视着我。

有没有比strtok更好的选择,我的孩子?她问道。

strtok正确地完成了我需要它完成的任务。您不是老提醒我C库函数也是C++标准的一部分吗?

是这样,我的学徒工。但记住,C标准对于库函数是不作承诺的。

好了,如果你想跟我谈论多线程的话题的话…”

不,我的孩子,”Guru打断说,我说的不是这个问题。神圣的C标准明确指出,strtok是不可再入的(事实上没有一个标准C函数能够做到这点)。更糟的是,在你这个情况,strtok在各级调用中都保持全局状态,考虑一下你的函数所运行的上下文环境,它在另一个解析函数的内部使用,而该解析函数也可能使用strtok

我用我最擅长的如车灯前的小鹿般的目光注视着她,让她明白我没怎么搞懂。

Guru点点头:考虑一个类似的例子,她边说边拿起干擦笔,在我的白书写板写道:

void f()

{

    // ...

 

    strtok( charPtrA, delimiters );

 

    // ...

 

    strtok( 0, delimiters );

 

    // ...

}

 

void g()

{

    strtok( charPtrB, otherDelimiters );

 

    // ...

 

    f();

 

    // ...

 

    strtok( 0, otherDelimiters );

}

f()结束的时候,它就会使stortok内部状态变得无法预料,g()就无法以它所预期的方式使用它。

我明白了,我说,所以,解决方法是……我还不想把皮球接过来。

用你自己的算法,在必要时做些改变。例如,这里是基本的strtok函数,它在std::string上操作,而不是C风格的字符串,并经过修改,使之不改变源字符串。

std::string StringTok(const std::string* newSeq,const std::string& delim )

{

    // 象标准的strtok一样,在各级调用中保持全局状态。

    // 要搜索的序列

 

    static const std::string* seq = 0;

 

    // 当前位置

 

    static std::string::size_type pos = 0;

 

    if( newSeq )

    {

        seq = newSeq;

        pos = 0;

    }

 

    std::string token;

 

    if( seq && pos != std::string::npos )

    {

        pos = seq->find_first_not_of( delim.c_str(), pos );

 

        if( pos != std::string::npos )

        {

            std::string::size_type next =

            seq->find_first_of( delim.c_str(), pos );

            token = seq->substr( pos, next-pos );

            pos = next;

 

            if( pos != std::string::npos )

                 ++pos;

 

            if( pos >= seq->size() )

                pos =std::string::npos;

        }

    }

 

    return token;

}

(我承认上面这些并非照抄Guru所写的内容,我不得不修改一些错字,清除一些逻辑错误。不过还是言归正传……

这是它的实现,”Guru继续道,它受到与我们前面所讨论的标准C strtok函数相同的大部分限制,并且带来了另外一些令人不安的东西,象创建一份标志字符串的负担。

但是难道不能采用copy-on-write吧?有些string的实现不是印证了这个情况,也就是说返加值是现存string的一个子串,而且仅作了一个copy-on-write?”

有些实现可能是这样做的,但是已有名言:copy-on-write使实现方法很难做到线程安全[2]现在,更多的实现方法遵循了该指示,去除了这些优化措施。另外,这并不是唯一的问题:该函数在异常安全方面做得也不好,比如在子串赋值给抛出的token的情况下。

所以,我继续道,我怎样克服各方面的问题呢?

Guru微斜的唇角流出一丝笑意:我的孩子,如果你打算超越学徒工这个阶段,你必须自己解决问题。克服这些问题的方法应该是,象我确信你的大学课本曾说过的,此题留作习题。写一个StringTok函数,消除可再入性和嵌套问题,修正异常安全问题,并尽可能地通过避免字符串拷贝来保持效率。下午我来看你的答案。

她转身离去,我接着干活。不久我就意识到要保持状态必须要使用一个对象。我写了个测试程序,并为我的解决方案写出代码,修正了一些bug后,我回头去找Guru。我最终交给她的代码如下:

template<class T>

class StringTok

{

public:

    StringTok( const T& seq,

    typename T::size_type pos = 0 )

    : seq_( seq ) , pos_( pos ) { }

 

    T operator()( const T& delim );

 

private:

    const T& seq_;

    typename T::size_type pos_;

};

 

template<class T>

T StringTok<T>::operator()( const T& delim )

{

    T token;

 

    if( pos_ != T::npos )

    {

    // 开始寻找标志

 

    typename T::size_type first =seq_.find_first_not_of( delim.c_str(), pos_ );

    if( first != T::npos )

    {

        // 所找到标志的长度

 

        typename T::size_type num =seq_.find_first_of( delim.c_str(), first ) - first;

        // 把所有的工作放在此处

 

        token = seq_.substr( first, num );

 

        //完成,现在提交只采用不抛出异常的操作

 

        pos_ = first+num;

        if( pos_ != T::npos ) ++pos_;

        if( pos_ >= seq_.size() ) pos_ = T::npos;

    }

}

 

return token;

}

用这种方法,我解释道,代码更简单了,因为我不必担心开始新序列。如果用户需要,他只需分创建一个新的tokenizer对象。现在异常安全的bug也消除了,因为我把所有的工作放在固定的地方,在提交时只采用不抛出异常的操作。哦,我想应该使它模板化……并不需要增加多少工作,只要多写几个typename罢了,用这种方法我们可以用宽字符来使用它。

Guru压了压耳后一络银灰色的头发。我知道了,她狡黠地说,“StringTok使用了缺省的拷贝构造函数和赋值操作。

我笑了,这次有充分的思想准备,不会上钩。事实上,我镇定地回答:我故意保留了缺省拷贝和赋值的用法,因为它们起到了书签的作用:用这种方法用户可以在标志化过程的任一位置取得StringTok的一份拷贝,并可从该位置起探寻另外的标志化方法。事实上,它在你原来的问题程序中工作良好,即使你通过使fg独立解析这个非常相似的字符串来增加问题的难度,它也毫无问题。

void f()

{

    // ...

 

    StringTok<std::string> x( stringA );

 

    // ...

 

    x( delimiters );

 

    // ...

}

 

void g()

{

    // ...

 

    // 这次字符串是一样的

    StringTok<std::string> y( stringA );

 

    // ...

 

    f();

 

    // ...

 

    // 记住我们的位置

    StringTok<std::string> z( y );

    y( otherDelimiters );

    if( DidntWork() )

    //再进入同一位置

    z( yetOtherDelimiters );

}

不仅各种标志化操作可以并行工作,而且它们可以在同一字符串上独立工作。所有这些,我总结道,不会增加任何开销,因为这些序列的字符串从不会被实际拷贝。我双手抱胸,又坐了下来。

说得好,”Guru微笑颔首,当你进一步深入时,你可能考虑增加更强大的特性,象在分隔符中使用vector<string>的能力,这样你就不会受限于单字符分隔符。

或者,我趁热打铁地说,允许它具有辨认空字段的能力。我以前曾试过strtok,它能解析出以逗号分隔的字段,但如果存在空字段,象A,B,,,C这样,就把它给搞糊涂了,strtok将会跳过两个空字段。

我们快速地对代码回顾作了总结,然后我就开始用修改后的解析函数来工作了。


要让仪器能分析出这台东西,还有些工作要做。珍妮摇摇头。

好了,跟上次测试比起来,我们还是有收获的。能量输入的各方面怎么样,跟上次我们测试时比不是有变化吗?

珍妮点点头:我知道了,让我们再试试。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值