﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-简单为美-随笔分类-C\C++</title><link>http://www.cppblog.com/len/category/7470.html</link><description /><language>zh-cn</language><lastBuildDate>Sun, 29 Jun 2008 12:51:30 GMT</lastBuildDate><pubDate>Sun, 29 Jun 2008 12:51:30 GMT</pubDate><ttl>60</ttl><item><title>Boost.Program_options简述</title><link>http://www.cppblog.com/len/archive/2008/06/15/53368.html</link><dc:creator>len</dc:creator><author>len</author><pubDate>Sun, 15 Jun 2008 13:03:00 GMT</pubDate><guid>http://www.cppblog.com/len/archive/2008/06/15/53368.html</guid><wfw:comment>http://www.cppblog.com/len/comments/53368.html</wfw:comment><comments>http://www.cppblog.com/len/archive/2008/06/15/53368.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/len/comments/commentRss/53368.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/len/services/trackbacks/53368.html</trackback:ping><description><![CDATA[
		<h3>介绍</h3>
		<p>命令行接口是普遍,基础的人机交互接口，从命令行提取程序的运行时选项的方法有很多。你可以自己编写相对应的完整的解析函数，或许你有丰富的C语言编程经验，熟知getopt()函数的用法，又或许使用Python的你已经在使用optparse库来简化这一工作。大家在平时不断地谈及到“不要重复造轮子”，那就需要掌握一些顺手的库，这里介绍一种C++方式来解析命令行选项的方法，就是使用Boost.Program_options库。</p>
		<p>program_options提供程序员一种方便的命令行和配置文件进行程序选项设置的方法。使用program_options库而不是你自己动手写相关的解析代码，因为它更简单，声明程序选项的语法简洁，并且库自身也非常小。将选项值转换为适合的类型值的工作也都能自动完成。库有着完备的错误检查机制，如果自己手写解析代码时，就可能会错过对一些出错情况的检查了。最后，选项值不仅能从命令行获取，也能从配置文件，甚至于环境变量中提取，而这些选择不会增加明显的工作量。</p>
		<h3>示例说明</h3>
		<p>以下面简单的hello程序进行说明，默认打印hello world,如果传入-p选项，就会打印出人的姓名，另外通过传入-h选项，可以打印出帮助选项。略微看一眼代码文件和相应的屏幕输入输出，然后我们再一起来看看这些是如何发生的。</p>
		<pre class="code">//hello.cpp 
#include &lt;iostream&gt;
#include &lt;string&gt;
#include &lt;boost/program_options.hpp&gt;

using namespace std;
int main(int argc, char* argv[])
{
    using namespace boost::program_options;
    //声明需要的选项
    options_description desc("Allowed options");
    desc.add_options()
        ("help,h", "produce help message")
        ("person,p", value&lt;string&gt;()-&gt;default_value("world"), "who")
        ;

    variables_map vm;        
    store(parse_command_line(argc, argv, desc), vm);
    notify(vm);    

    if (vm.count("help")) {
        cout &lt;&lt; desc;
        return 0;
    }
    cout &lt;&lt; "Hello " &lt;&lt; vm["person"].as&lt;string&gt;() &lt;&lt; endl;
    return 0;
}
</pre>
		<p>下面是在Windows命令提示符窗口上的输入输出结果，其中"&gt;"表示提示符。</p>
		<pre class="console">&gt;hello 
Hello world

&gt;hello -h
Allowed options:
  -h [ --help ]                produce help message
  -p [ --person ] arg (=world) who

&gt;hello --person len
Hello len
</pre>
		<p>首先通过options_description类声明了需要的选项，<tt>add_options</tt>返回了定义了<tt>operator()</tt>的特殊的代理对象。这个调用看起来有点奇怪，其参数依次为选项名，选项值，以及选项的描述。注意到示例中的选项名为"help,h"，是因为声明了具有短选项名和长选项名的选项，这跟gnu程序的命令行具有一致性。当然你可以省略短选项名，但是这样就不能用命令选项简写了。第二个选项的声明，定义了选项值为<tt>string</tt>类型，其默认值为<tt>world.</tt></p>
		<p>接下来,声明了<tt>variables_map</tt>类的对象，它主要用来存储选项值，并且能储存任意类型的值。然后，<tt>store,parse_command_line和notify</tt>函数使<tt>vm</tt>能存储在命令行中发现的选项。</p>
		<p>最后我们就自由地使用这些选项了，<tt>variables_map</tt>类的使用就像使用<tt>std::map</tt>一样，除了它必须用<tt>as</tt>方法去获取值。如果as方法调用的指定类型与实际存储的类型不同，就会有异常抛出。</p>
		<p>具有编程的你可能有这样的经验，使用<tt>cl或gcc</tt>对源文件进行编译时，可直接将源文件名放置在命令行中，而无需什么选项字母，如<tt>gcc a.c</tt>之类的。prgram_options也能处理这种情况，在库中被称为"positional options"(位置选项),但这需要程序员的一点儿帮助才能完成。看下面的经过对应修改的代码，我们无需传入"-p"选项，就能可指定"person"选项值</p>
		<pre class="code">    positional_options_description p;
    p.add("person", -1);
    store(command_line_parser(argc, argv).options(desc).positional(p).run(), vm);
</pre>
		<pre class="console">&gt;hello len
Hello len
</pre>
		<p>前面新增的两行是为了说明所有的位置选项都应被解释成"person"选项，这里还采用了<tt>command_line_parser</tt>类来解析命令行，而不是用parse_command_line函数。后者只是对前者类的简单封装，但是现在我们需要传入一些额外的信息，所以要使用类本身。</p>
		<h3>选项复合来源</h3>
		<p>一般来说，在命令行上指定所有选项，对用户来说是非常烦人的。如果有些选项要应用于每次运行，那该怎么办呢。我们当然希望能创建出带有些常用设置的选项文件，跟命令行一起应用于程序中。当然这一切需要将命令行与配置文件中的值结合起来。比如，在命令行中指定的某些选项值应该能覆盖配置文件中的对应值，或者将这些值组合起来。</p>
		<p>下面的代码段将选项通过文件读取，这文件是文本格式，可用"#"表示注释，格式如命令行中的参数一样，选项=值</p>
		<pre class="code">    ifstream ifs("config.cfg");
    store(parse_config_file(ifs,config),vm);
    notify(vm);
</pre>
		<h3>参考</h3>
		<p>
		</p>
		<p>
				<a title="boost.program_options lib document" href="http://www.boost.org/doc/libs/1_35_0/doc/html/program_options.html">Boost.prgram_options库文档</a>
		</p>
<img src ="http://www.cppblog.com/len/aggbug/53368.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/len/" target="_blank">len</a> 2008-06-15 21:03 <a href="http://www.cppblog.com/len/archive/2008/06/15/53368.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Boost.Lambda简述</title><link>http://www.cppblog.com/len/archive/2008/05/18/50286.html</link><dc:creator>len</dc:creator><author>len</author><pubDate>Sun, 18 May 2008 08:03:00 GMT</pubDate><guid>http://www.cppblog.com/len/archive/2008/05/18/50286.html</guid><wfw:comment>http://www.cppblog.com/len/comments/50286.html</wfw:comment><comments>http://www.cppblog.com/len/archive/2008/05/18/50286.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/len/comments/commentRss/50286.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/len/services/trackbacks/50286.html</trackback:ping><description><![CDATA[
		<h3>Boost.Lambda是什么?</h3>
		<p>Boost Lambda库是C++模板库,以C++语言实现了lambda抽象.Lambda这个术语来自函数编程语言和lambda闭包理论,lambda抽象实际上定义了匿名函数.了解过C#新引入的匿数函数特性或Lisp编程的人，对这些概念理解会有很大帮助.Lambda库设计的主要动机是为STL算法提供灵活方便的定义匿名函数对象的机制.这个Lambda库究竟是有什么用呢?代码胜千言!看下面将STL容器中的元素打印到标准输出上的代码.</p>
		<pre class="code">for_each(a.begin(), a.end(), std::cout &lt;&lt; _1 &lt;&lt; ' ');</pre>
		<p>
		</p>
		<p>表达式<tt>std::cout &lt;&lt; _1 &lt;&lt; ' '</tt>定义了一元函数对象.变量<tt>_1</tt>是函数的形参,是实参的占位符.每次for_each的迭代中,函数带着实际的参数被调用,实际参数取代了占位符,然后函数体里的内容被执行.Lambda库的核心就是让你能像上面所展示的那样,在STL算法的调用点,定义小的匿名函数对象. </p>
		<h3>Lambda库的安装</h3>
		<p>Lambda库只由头文件组成,这就意味着你不需要进行任何编译,连接,生成二进制库的动作,只需要boost库头文件路径包含进你的工程中即可使用.</p>
		<p>与现代的C++语言一样,在使用时你需要声明用到的名字空间,把下列的代码包含在你的源文件头:</p>
		<pre class="code">using namespace boost::lambda;</pre>
		<h3>Boost Lambda库的动机</h3>
		<p>在标准模板库STL成为标准C++的一部分后,典型的STL算法对容器中元素的操作大都是通过函数对象(<tt>function objects</tt>)完成的.这些函数作为实参传入STL算法.</p>任何C++中以函数调用语法被调用的对象都是函数对象.STL对某些常见情况预置了些函数对象.比如:<tt>plus,less,not1</tt>下面就是标准<tt>plus</tt>模板的一种可能实现: <pre class="code">template &lt;class T&gt; 
struct plus : public binary_function &lt;T, T, T&gt; {
  T operator()(const T&amp; i, const T&amp; j) const {
    return i + j; 
  }
};
</pre><p>基类<tt>binary_function&lt;T, T, T&gt;</tt>包含了参数和函数对象返回类型的类型定义,这样可使得函数对象可配接.</p><p></p>除了上面提到的基本的函数对象外,STL还包含了<tt>binder</tt>模板,将可配接的二元函数中的某个实参固定为常量值,来创建一个一元函数对象.比如: 
<p></p><pre class="code">class plus_1 {
  int _i;
public:
  plus_1(const int&amp; i) : _i(i) {}
  int operator()(const int&amp; j) { return _i + j; }
};
</pre><p>上面的代码显性地创建了一个函数对象,将其参数加1.这样的功能可用<tt>plus</tt>模板与binder模板(<tt>bind1st</tt>来等效地实现.举例来说,下面的两行表达式创建了一个函数对象,当它被调用时,将返回1与调用参数的和.</p><pre class="code">plus_1(1)
bind1st(plus&lt;int&gt;(), 1)
</pre><p><tt>plus&lt;int&gt;</tt>就是计算两个数之和的函数对象.<tt>bind1st</tt>使被调用的函数对象的第一个参数绑定到常量1.作为上面函数对象的使用示例,下面的代码就是将容器<tt>a</tt>中的元素加1后,输出到标准输出设备:</p><pre class="code">transform(a.begin(), a.end(), ostream_iterator&lt;int&gt;(cout),
          bind1st(plus&lt;int&gt;(), 1));
</pre><p></p>为了使binder更加通用,STL包含了适配器<tt>(adaptors)</tt>用于函数引用与指针,以及成员函数的配接. 
<p></p><p>所有这些工具都有一个目标,就是为了能在STL算法的调用点有可能指定一个匿名的函数,换句说,就是能够使部分代码片断作为参数传给调用算法函数.但是,标准库在这方面只做了部分工作.上面的例子说明用标准库工具进行匿名函数的定义还是很麻烦的.复杂的函数调用表达式,适配器,函数组合符都使理解变得困难.另外,在运用标准库这些方法时还有明显的限束.比如,标准C++98中的binder只允许二元函数的一个参数被绑定,而没有对3参数,4参数的绑定.这种情况在TR1实施后,引进了通用的binder后可能改善,对于使用MSVC的程序员,有兴趣还可以查看下微软针对VS2008发布的TR1增强包.</p><p>但是不管怎样,Lambda库提供了针对这些问题比较优雅的解决方法:</p><ul><li><p>对匿名函数以直观的语义进行创建,上面的例子可改写成:</p><pre class="code">transform(a.begin(), a.end(), ostream_iterator&lt;int&gt;(cout), 
          1 + _1);
</pre><p>更直观点:</p><pre class="code">for_each(a.begin(), a.end(), cout &lt;&lt; (1 + _1));
</pre></li><li><p>绝大部分对函数参数绑定的限制被去除,在实际C++代码中可以绑定任意的参数</p></li><li><p>分离的函数组合操作不再需要了,函数组合被隐性地支持.</p></li></ul><h3>Lambda表达式介绍</h3><p>Lambda表达在函数式编程语言中很常见.在不同语言中,它们的语法有着很大不同,但是lambda表达式的基本形式是:</p><pre class="code">lambda x<sub>1</sub>...x<sub>n</sub>.e</pre><p>lambda表达式定义了匿名函数,并由下列的元素组成</p><ul><li>函数的参数:x<sub>1</sub>...x<sub>n</sub></li><li>表达式e,以参数x<sub>1</sub>...x<sub>n</sub>的形式计算函数的值</li></ul><p>一个简单的lambda表达式的例子是:</p><pre class="code">(lambda x y.x+y) 2 3 = 2 + 3 = 5 </pre><p>在lambda表达式的C++版本中,表达式中x<sub>1</sub>...x<sub>n</sub>不需要,已预定义形式化的参数.在现在Boost.Lambda库中,存在三个这样的预定义的参数,叫做占位符:<tt>_1,_2,和_3</tt>.它们分别指代在lambda表达式中的第一,二,三个参数.比如,下面这样的lambda表达式:</p><pre class="code">lambda x y.x+y</pre><p>C++定义的形式将会是这样:</p><pre class="code">_1 + _2</pre><p>因此在C++中的lambda表达式没有语义上所谓的关键字.占位符作为运算符使用时就隐性地意味着运算符调用是个lambda表达式.但是只有在作为运算符调用才是这样.当Lambda表达式包含函数调用,控制结构,转换时就需要特殊的语法调用了.更为重要的是,作为函数调用是需封装成binder函数的形式.比如,下面这个lambda表达式:</p><pre class="code">lambda x y.foo(x,y)</pre><p>不应写成foo(_1,_2),对应的C++结构应如下:</p><pre class="code">bind(foo, _1, _2)</pre><p>对于这种表达式,更倾向于作为绑定表达式<tt>bind expressions</tt></p><p>lambda表达式定义了C++的函数对象,因此,对于函数调用的形式跟其他的函数对象一样,比如:<tt>(_1 + _2)(i, j)</tt>.</p><h3>性能</h3><p>性能,运行效率,总是C++程序员关心的话题.理论上,相对于手写循环代码,使用STL算法和Lambda函数对象的所有运行开销,可以通过编译优化消除掉.这种优化取决于编译器,实际中的编译器大都能做到.测试表明,性能会有下降,但是影响不大,对于代码的效率和简洁之间的权衡,只能由程序员自己做出判断了.</p><p>Lambda库的设计与实现中大量运用了模板技术,造成对于同一模板需要大量的递归实例化．这一因素可能使构建复杂逻辑的lambda表达式，不是一个非常理想的做法．因为编译这些表达式需要大量的内存，从而使编译时间变得非常慢，这在一些大型项目中会更加突出．还有在发生编误错误时，引发的大量错误信息，不能有效地指出真正错误之处．最后点，C++标准建议模板的嵌套层次不要超过17层来防止导致无限递归,而复杂的Lambda表达式模板会很容易超过这一限制．虽然大多数编译器允许更深层次的模板嵌套，但是通常需要显性地传入一个命令行参数才能做到．</p><h3>参考</h3><p>大多数内容是从<a href="http://www.boost.org/doc/libs/1_35_0/doc/html/lambda.html">Boost.Lambday库在线文档</a>参考翻译而成</p><img src ="http://www.cppblog.com/len/aggbug/50286.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/len/" target="_blank">len</a> 2008-05-18 16:03 <a href="http://www.cppblog.com/len/archive/2008/05/18/50286.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>(翻译)设计Qt风格的C++的应用程序接口</title><link>http://www.cppblog.com/len/archive/2008/05/11/49563.html</link><dc:creator>len</dc:creator><author>len</author><pubDate>Sun, 11 May 2008 12:07:00 GMT</pubDate><guid>http://www.cppblog.com/len/archive/2008/05/11/49563.html</guid><wfw:comment>http://www.cppblog.com/len/comments/49563.html</wfw:comment><comments>http://www.cppblog.com/len/archive/2008/05/11/49563.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.cppblog.com/len/comments/commentRss/49563.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/len/services/trackbacks/49563.html</trackback:ping><description><![CDATA[
		<p>
				<strong>"Designing Qt-Style C++ APIs" by Matthias Ettrich</strong>
		</p>
		<p>
				<a title="http://doc.trolltech.com/qq/qq13-apis.html" href="http://doc.trolltech.com/qq/qq13-apis.html">http://doc.trolltech.com/qq/qq13-apis.html</a>
				<br />
		</p>
		<p>
				<em>翻译这篇文章的目的不是让人了解Qt，而是让人试着学习点C++编程的软技能。我从原文中得到的一些风格上的体会，也希望你能从中有所收获.（译者注)</em>
				<br />
				<strong>我们在Trolltech做了大量研究来改进Qt开发体验.在这篇文章中,我将分享我们的一些成果,呈现我们在进行Qt 4设计时所使遵循的原现，并向你展示如何将它们应用到你的代码中．</strong>
		</p>
		<ul>
				<li>
						<a href="#sixcharacteristicsofgoodapis">优秀API的六个特性</a>
				</li>
				<li>
						<a href="#theconveniencetrap">方便性陷阱</a>
				</li>
				<li>
						<a href="#thebooleanparametertrap">布尔参数陷阱</a>
				</li>
				<li>
						<a href="#staticpolymorphism">静态多态</a>
				</li>
				<li>
						<a href="#theartofnaming&quot;" temp_href="#theartofnaming&quot;">命名艺术</a>
				</li>
				<li>
						<a href="pointersorreferences">指针或引用?</a>
				</li>
				<li>
						<a href="#casestudyqprogressbar">案例分析:QProgressBar</a>
				</li>
				<li>
						<a href="#howtogetapisright">怎样写出正确的API</a>
				</li>
		</ul>
		<p>设计应用程序接口(APIs)是有难度的.它是像跟设计编程语言一样困难的艺术．要遵循许多不同的的原则，这些原则中的许多还彼此冲突.</p>
		<p>现今的计算机教育过多关注于算法和数据结构，很少去关注隐藏在程序设计语言和程序框架后面的那些设计原则．这使得程序员们面对日益重要的任务,创建可复用的组件,毫无准备.</p>
		<p>在面向对象语言出现前,通用的可复用的代码大都由库提供者而不是应用程序开发者来编写.在Qt世界中，这种情况已发生了很大的变化．在用Qt编程其实就是在写新的组件.典型的Qt应用程序都存在某些自定义的组件，在整个应用程序中被复用.相同的组件常常作为其他程序的一部分被开发出来．KDE,K桌面环境,甚至使用许多附加库，来进一步扩展Qt，实现许多额外的类.</p>
		<p>但是一个优秀,高效的C++ API究竟是怎样子呢?它的好坏取决于许多因素,比如说，手头上的任务和特定目标群体.优秀的API具有很多特性,它们的一些是普遍所要期望的,另一些是针对特定问题域的.</p>
		<a name="sixcharacteristicsofgoodapis">
		</a>
		<h3>优秀API的六个特性 </h3>
		<p>API对于程序员就相当于GUI对于最终用户.API中'P'代表程序员(Programmer)，而不是程序(Program)，强调这一点是为了说明API是让程序员使用的,程序员是人而不机器. </p>
		<p>我们认为APIs应当精简而完备,具有清晰简单的语义，直观,易记且应使代码具有可读性.</p>
		<ul>
				<li>
						<strong>精简性</strong>:精简的API具有尽可能少的类和公共成员.这使得理解，记忆，调试，更改API更加容易. 
</li>
				<li>
						<strong>完备性</strong>：完备的API意味着拥有应具有的期望功能.这可能使与API保持精简性相冲突.还有，如果成员函数放在不相匹配的类中，那么许多使用这个功能函数的潜在用户会找不到它． 
</li>
				<li>
						<strong>清晰简单的语义</strong>:正如与其他设计工作一样，你应该准守最小惊议原则.让通常的任务简单，罕见的任务应尽可能简单，但它不应成为重点.解决特定的问题．不要使解决方法具有普适作用，当它们不需要的时候. 
</li>
				<li>
						<strong>直观性</strong>:与计算机有关的其他事情一样，API应具有直观性.不同经历和背景会导致对哪些是直观，哪些不是直观的不同看法．如果对非专业的用户在不需要阅读文档下能立即使用API,或对这个API不了解的程序员能理解使用了API的代码，那么这API就是具有直观性. 
</li>
				<li>
						<strong>易记</strong>:为了使API容易记忆，使用一致且精准的命名规范.使用容易识别的模式和概念，避免使用缩写. 
</li>
				<li>
						<strong>能生成可读生代码</strong>:代码只写一遍，却要阅读许多遍(调试或更改).可读性的代码有时候可能需要多敲些字,但是从产品生命周期中可节省很多时间. </li>
		</ul>
		<p>最后，请记住：不同的用户使用API的不同部分.当简单地使用Qt类的实例可能有直观性，但这有可能使用户在阅读完有关文档后，才能尝试使用其中部分功能.</p>
		<a name="theconveniencetrap">
		</a>
		<h3>方便性陷阱</h3>
		<p>通常的误读是越少的代码越能使你达到编写更好的API这一目的.请记住，代码只写一遍，却要一遍又一遍地去理解阅读它.比如:</p>
		<pre class="code">QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical,
                                  0, "volume");
</pre>
		<p>
		</p>
		<p>可以会比下面的代码更难阅读(甚至于编写)</p>
		<p>
		</p>
		<pre class="code">
QSlider *slider = new QSlider(Qt::Vertical);
slider-&gt;setRange(12, 18);
slider-&gt;setPageStep(3);
slider-&gt;setValue(13);
slider-&gt;setObjectName("volume");
</pre>
		<p>
		</p>
		<a name="thebooleanparametertrap">
		</a>
		<h3>布尔参数陷阱</h3>
		<p>布尔参数常常导致难以阅读的代码.特别地，增加某个bool参数到现存的函数一般都会是个错误的决定．在Qt中，传统的例子是<tt>repaint()</tt>,它带有一个可选的布尔参数，来指定背景是否删除(默认是删除)．这就导致了代码会像这样子:</p>
		<pre class="code">
widget-&gt;repaint(false);</pre>
		<p>初学者可能会按字面义理解为,"不要重绘!"</p>
		<p>自然的想法是<tt>bool参数节省了一个函数,因此减少了代码的臃肿.事实上,这增加了代码的臃肿，有多少Qt用户真正知道下面这三行代码在做什么呢？</tt></p>
		<pre class="code">
widget-&gt;repaint();
widget-&gt;repaint(true);
widget-&gt;repaint(false);
</pre>
		<p>好一点的API代码可能看起来像这样：</p>
		<pre class="code">
widget-&gt;repaint();
widget-&gt;repaintWithoutErasing();
</pre>
		<p>在Qt 4中，我们解决这个问题的办法是，简单地去除掉不删除<tt>widget</tt>而进行重绘的可能性.Qt 4对双重缓冲的原生支持，会使这功能被废弃掉.</p>
		<p>这里有些例子：</p>
		<p>
		</p>
		<pre class="code">
widget-&gt;setSizePolicy(QSizePolicy::Fixed,
                          QSizePolicy::Expanding, true);
textEdit-&gt;insert("Where's Waldo?", true, true, false);
QRegExp rx("moc_*.c??", false, true);
</pre>
		<p>显然的解决办法就是将<tt>bool</tt> 参数用枚举类型来替换.这就是我们在Qt 4中Qstring中的大小写敏感所做的,比较下面两个例子:</p>
		<pre class="code">
str.replace("%USER%", user, false);               // Qt 3
str.replace("%USER%", user, Qt::CaseInsensitive); // Qt 4
</pre>
		<a name="staticpolymorphism">
		</a>
		<h3>静态多态 </h3>
		<p>相似的类应该有相似的API.在某种程度上,这能用继承来实现,也就是运用运行时多态机制.但是多态也能发生在设计时.比如,你将QListBox与QComboBox交换,QSlider与QSpinBox交换,你会发现API的相似性会使这种替换变得比较容易.这就是我们所谓的"静态多态". </p>
		<p>静态多态也能使记忆API和编程模式更加容易.因而,对一组相关类的相似API有时候比为每个类设计独特完美的API会更好. </p>
		<a name="theartofnaming">
		</a>
		<h3>命名艺术</h3>
		<p>命名有时候是设计API中最重要的事情了.某个类应叫什么名字,某个成员函数又应叫什么名字,都需要好好思考.  </p>
		<h4>通常的命名规则</h4>
		<p>有少许规则对所有类型的命名都适应.首先,正如我早先所提到的,不要用缩写．甚至对用"prev"代表"previous"这样明显的缩写也不会在长期中受益，因为用户必须记住哪些名字是缩写． </p>
		<p>如果连API自身都不能保持统一,事情自然会变得更坏．比如，Qt 3中有<tt>activatePreviousWindow()函数，也有<tt>fetchPrev()函数</tt>.坚持"没有缩写"这条规则，会使创建一致的API更加简单． </tt></p>
		<p>在设计类中，另一重要但是不明显的规则是尽量保持子类中名字的简洁易懂．在Qt 3中，这个原则并不总是被遵守．为了说明这一点，我们举下QToolButton的例子.如果你在Qt 3中对QToolButton调用call <tt>name()</tt>, <tt>caption()</tt>, <tt>text()</tt>, 或 <tt>textLabel()成员函数时，你希望会发生什么?那就在Qt设计器中试试QToolButton吧．</tt></p>
		<ul>
				<li>
						<tt>name</tt> 属性继承自QObject，用来在调试和测试中指代对象的内部名称． 
</li>
				<li>
						<tt>caption</tt> 属性继承自QWidget，指代窗体的标题．对于QToolButton没有什么意思，既然它们都是由父窗体创建的． 
</li>
				<li>
						<tt>text</tt> 属性继承自QButton，通常用于按钮中，除非useTextLabel为真． 
</li>
				<li>
						<tt>textLabel</tt> 属性 在QToolButton中声明，如果<tt>useTextLabel为真，则显示在按钮上．</tt></li>
		</ul>
		<p>为了可读性的关系，在Qt4中<tt>name</tt> 被称为<tt>objectName</tt> ,<tt>caption被称为<tt>windowTitle</tt>,在QToolButton中为了使<tt>text</tt>明晰，不再有<tt>textLabel属性.</tt></tt></p>
		<h4>命名类</h4>
		<p>不应为每个不同的类寻求完美的名字，而是将类进行分给．比如，在Qt 4中所有跟模型有关的视类的部件都用<tt>View</tt>后缀(QlistView,QTableView,QTreeView)，相应的基于部件的类用<tt>Widget</tt>后缀代替(QListWidget,QTableWidget,QTreeWidge). </p>
		<h4>枚举类型和值类型命名</h4>
		<p>当设计枚举时，我们应当记住C++中(不像Java或C#)，枚举值在使用时不带类型名.下面的例子说明了对枚举值取太一般化的名字的危害：</p>
		<pre class="code">
namespace Qt
{
    enum Corner { TopLeft, BottomRight, ... };
    enum CaseSensitivity { Insensitive, Sensitive };
    ...
};
    
    tabWidget-&gt;setCornerWidget(widget, Qt::TopLeft);
    str.indexOf("$(QTDIR)", Qt::Insensitive);
</pre>
		<p>在上面这行中，<tt>Insensitive这个名字什么意思呢?为枚举类型命名具有指导的原则是最好在每个枚举值中重复枚举类型的名字．</tt></p>
		<pre class="code">
namespace Qt
{
    enum Corner { TopLeftCorner, BottomRightCorner, ... };
    enum CaseSensitivity { CaseInsensitive,
                              CaseSensitive };
    ...
};
    
tabWidget-&gt;setCornerWidget(widget, Qt::TopLeftCorner);
str.indexOf("$(QTDIR)", Qt::CaseInsensitive);
    </pre>
		<p>但枚举值之间是一种"或"关系和被用作标志位时，传统的解决方法是将"或"结果存为<tt>int</tt>,这样做是类型不安全的.Qt 4提供了一模板类QFlags&lt;T&gt;，其中T是枚举类型.Qt为标志类型名称提供了便利，你能用<tt>Qt::Alignment</tt> 来代替QFlags&lt;Qt::AlignmentFlag&gt;. </p>
		<p>为了方便,我们给枚举类型单数形式的名称(只有当只含一个标志位时)，给"flags"类型复数形式的名称，比如：</p>
		<pre class="code">
enum RectangleEdge { LeftEdge, RightEdge, ... };
typedef <a href="http://doc.trolltech.com/4.0/qflags.html">QFlags</a>&lt;RectangleEdge&gt; RectangleEdges;
    </pre>
		<p>在某些情况下,"flags"类型有单数形式的名称.在这种情况下，枚举类型以<tt>Flag</tt>后缀标识:</p>
		<pre class="code">
enum AlignmentFlag { AlignLeft, AlignTop, ... };
typedef <a href="http://doc.trolltech.com/4.0/qflags.html">QFlags</a>&lt;AlignmentFlag&gt; Alignment;
</pre>
		<h4>函数和参数的命名</h4>
		<p>函数命名中的一条规则就是应能从它的名字清楚地看出函数是否着副作用.在Qt 3中,常函数QString::simplifyWhiteSpace()就违反了这规则.即然它返回QString,而不是像它的名字所表述的那样修改字符串. 在Qt 4中,这个函数被重命名为QString::simplified(). </p>
		<p>参数名对于程序员来说是重要的信息来源,即使它们不出现在调用API的代码中.既然现代的IDE会在程序员编码时显示这些参数，所以非常值得在头文件中给这些参数取恰当的名字，在文档中同样使用相同的名字 </p>
		<h4>给布尔型的getter,setter,属性的命名</h4>
		<p>给布尔型的getter,setter,属性取个恰当的名字总是特别困难．getter应该叫<tt>checked()</tt> 或者还是叫<tt>isChecked(),取<tt>scrollBarsEnabled()</tt>还是<tt>areScrollBarEnabled()</tt></tt></p>
		<p>在Qt 4中，我们对于getter的函数使用下面的指导原则 </p>
		<ul>
				<li>形容词就使用<tt>is</tt>-前缀.比如: 
<ul><li><tt>isChecked()</tt></li><li><tt>isDown()</tt></li><li><tt>isEmpty()</tt></li><li><tt>isMovingEnabled()</tt></li></ul>但是形容词应用到复数形式的名词没有前缀: 
<ul><li><tt>scrollBarsEnabled()</tt>, not <tt>areScrollBarsEnabled()</tt></li></ul></li>
				<li>动词没有前缀，也不使用第三人称的(-s): 
<ul><li><tt>acceptDrops()</tt>, not <tt>acceptsDrops()</tt></li><li><tt>allColumnsShowFocus()</tt></li></ul></li>
				<li>名词性的通常没有前缀: 
<ul><li>用<tt>autoCompletion()</tt>, 不用<tt>isAutoCompletion()</tt></li><li><tt>boundaryChecking()</tt></li></ul>有时候没有前缀会产生误导，在这种情就加上前缀<tt>is</tt>-: 
<ul><li><tt>isOpenGLAvailable()</tt>, not <tt>openGL()</tt></li><li><tt>isDialog()</tt>, not <tt>dialog()</tt></li></ul>(如果函数叫做dialog()，我们通常会认定它会返回QDialog*类型) </li>
		</ul>
		<p>setter的命名可以从这推知，只要去掉is前缀，在名字前面加set前缀就可以了.比如<tt>setDown()</tt>和<tt>setScrollBarsEnabled().属性的名字跟getter一样，就是没有is前缀</tt></p>
		<a name="pointersorreferences">
		</a>
		<h3>指针或引用?</h3>
		<p>对于向外传参,是使用指针,还是引用更好呢? </p>
		<pre class="code">
void getHsv(int *h, int *s, int *v) const
void getHsv(int &amp;h, int &amp;s, int &amp;v) const
</pre>
		<p>绝大多数C++书籍都推荐无论何时都尽可能使用引用，因为从大多数情况来说，引用比指针有着所谓的"安全和优雅".相比而方，在Trolltech，我们更趋向于指针，因为它使用户代码更具可读性．比较下面的代码：</p>
		<pre class="code">
color.getHsv(&amp;h, &amp;s, &amp;v);
color.getHsv(h, s, v);
</pre>
		<p>只有第一行代码能更清楚地说明h,s,v在函数被调用后，其值极有可能被修改. </p>
		<a name="casestudyqprogressbar">
		</a>
		<h3>案例分析：QProgressBar</h3>
		<p>为了在实际代码中说明这些概念，我们以QProgressBar在Qt3和Qt4中的比较进行研究.在Qt 3中:</p>
		<pre class="code">
class QProgressBar : public QWidget
{
  ...
    public:
        int totalSteps() const;
        int progress() const;
    
        const QString &amp;progressString() const;
        bool percentageVisible() const;
        void setPercentageVisible(bool);
    
        void setCenterIndicator(bool on);
        bool centerIndicator() const;
    
        void setIndicatorFollowsStyle(bool);
        bool indicatorFollowsStyle() const;
    
    public slots:
        void reset();
        virtual void setTotalSteps(int totalSteps);
        virtual void setProgress(int progress);
        void setProgress(int progress, int totalSteps);
    
    protected:
        virtual bool setIndicator(QString &amp;progressStr,
                                  int progress,
                                  int totalSteps);
        ...
    };
    </pre>
		<p>
		</p>
		<p>对这个API进行改进的关键之处就是需要观察到Qt 4中QProgressBar与QAbstractSpinBox，以及它的子类,QSpinBox,QSlider,和QDial有着相似性.解决的办法呢?将其中的progress和totalSteps用minimun,maximum和value替换. </p>
		<p>增加<tt>valueChanged()</tt>的信号量.增加<tt>setRange()</tt>这一方便的函数. </p>
		<p>接下来需要到<tt>progressString</tt>, <tt>percentage</tt> 和<tt>indicator实际上都指代同一东西：显示在进度栏上的文本.通常这一文本是一百分数,但是它能被<tt>setIndicator()</tt>设置成任何值.这里是新的API:</tt></p>
		<pre class="code">
virtual QString text() const;
void setTextVisible(bool visible);
bool isTextVisible() const;
</pre>
		<p>默认，这文本是百分比指示器.这可以用重新实现的<tt>text()进行改变．</tt></p>
		<p>
				<tt>在Qt 3中，setCenterIndicator()</tt> 和 <tt>setIndicatorFollowsStyle()是两个影响对齐方式的函数.它们现在都被一个高级的函数所取代,<tt>setAlignment()</tt>．</tt></p>
		<pre class="code">
void setAlignment(Qt::Alignment alignment);
</pre>
		<p>如果程序员没有调用 <tt>setAlignment()</tt>,对齐是基于的样式决定的．对于Motif样式，文本显示在中间，而对于其他样式，文本是右对齐的． </p>
		<p>这里是改进过的QProgressBar:</p>
		<pre class="code">
class QProgressBar : public QWidget32
{
        ...
    public:
        void setMinimum(int minimum);
        int minimum() const;
        void setMaximum(int maximum);
        int maximum() const;
        void setRange(int minimum, int maximum);
        int value() const;
    
        virtual QString text() const;
        void setTextVisible(bool visible);
        bool isTextVisible() const;
        Qt::Alignment alignment() const;
        void setAlignment(Qt::Alignment alignment);
    
    public slots:
        void reset();
        void setValue(int value);
    
    signals:
        void valueChanged(int value);
        ...
};</pre>
		<a name="howtogetapisright">
		</a>
		<h3>怎样写出正确的APIs</h3>
		<p>APIs需要质量保证．最早的版本一般都不是很好的，你必须测试它．通过调用这个API的代码作为测试事例，来验证代码具有可读性. </p>
		<p>另外的技巧包括让人在没有文档和类文档化(类的概述和函数说明)的情况下能够使用这个API. </p>
		<p>当你陷入麻烦中时，文档化也是好的办法找出一个合适的命名：试着为这些类，函数，枚举值标住文档，然后使用浮现在你脑中的第一个词汇.如果你找不到精准的名字去表述，那很有可能这个东西就不应存在．如果任何办法都失败了，而且你确信这个概念是有用的，那就发明一个新的名字吧．最后，不管怎么说,"widget", "event", "focus", and "buddy"这些词总会能用上一个.</p>
<img src ="http://www.cppblog.com/len/aggbug/49563.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/len/" target="_blank">len</a> 2008-05-11 20:07 <a href="http://www.cppblog.com/len/archive/2008/05/11/49563.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Say Hello to GTK+</title><link>http://www.cppblog.com/len/archive/2008/03/23/45190.html</link><dc:creator>len</dc:creator><author>len</author><pubDate>Sun, 23 Mar 2008 07:18:00 GMT</pubDate><guid>http://www.cppblog.com/len/archive/2008/03/23/45190.html</guid><wfw:comment>http://www.cppblog.com/len/comments/45190.html</wfw:comment><comments>http://www.cppblog.com/len/archive/2008/03/23/45190.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/len/comments/commentRss/45190.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/len/services/trackbacks/45190.html</trackback:ping><description><![CDATA[
		<p>Windows下的C++程序员在开发图形用户界面时,首先想到可能就是MFC了.对于<a href="http://www.gtk.org/">GTK+</a>这种GNU/Linux上出生出来的东西,就感到陌生了．GTK+是类似于MFC的图形界面库,跟MFC不同的是,它不是用C++,而是用C语言实现了面对对象的机制,但能与许多语言绑定,并具有跨平台的特性.比如与<a href="http://www.python.org/">Python</a>的结合,就产生<a href="http://www.pygtk.org/">PyGTK</a>,"<a href="/len/archive/2008/03/12/44296.html">初识PyGTK"</a>就介结了其在Windows平台的安装.</p>
		<p>在Windows开发GTK+的应用程序,首先需要其在Windows版本下的库文件,下载<a href="http://sourceforge.net/projects/gladewin32">gladewin32</a>项目中的<a href="http://sourceforge.net/project/showfiles.php?group_id=98754&amp;package_id=111411">gtk+-win32-devel</a>安装包进行安装.我这里使用VS2005进行配置，因为相对于其他的环境，大家对于此IDE更为熟悉．接下来我将一步步介绍我在对GTK/Glande3说Hello时碰到的问题与解决方法．</p>
		<h3>丑陋的Hello World</h3>
		<p>任何像我一样急切的人，都希望用最少的代码，看一下GTK+的世界是怎么样的．新建一"Win32 项目",然后向其添加gtk.c文件.</p>
		<pre class="code">
/*  file gtk.c  */ 
#include  &lt; gtk / gtk.h &gt; 

int  main( int  argc,  char *  argv[])
{
    GtkWidget *  window;
    gtk_init ( &amp; argc,  &amp; argv);

    window  =  gtk_window_new (GTK_WINDOW_TOPLEVEL);
    
    gtk_window_set_title (GTK_WINDOW (window),  " Hello World " );
    gtk_widget_show (window);
    gtk_main();

    return   0 ;
} 
</pre>
		<p>现在麻烦的事情到了,设置相关的头文件和库文件.设置头文件,右键点击项目名,然后选择属性.然后在"常规"-"附加包含目录"中设置你安装gtk+-win32-devel时对应的头文件目录.然后再设置库文件,需要gtk-win32-2.0.lib,glib-2.0.lib,gobject-2.0.lib.</p>
		<img height="432" alt="p1_4.jpg" src="http://www.cppblog.com/images/cppblog_com/len/WindowsLiveWriter/f5af5ed389e0_9458/p1_4.jpg" width="640" border="0" />
		<br />
		<img height="234" alt="p2_thumb.jpg" src="http://www.cppblog.com/images/cppblog_com/len/WindowsLiveWriter/f5af5ed389e0_9458/p2_thumb.jpg" width="340" border="0" />
		<img height="234" alt="p3_thumb.jpg" src="http://www.cppblog.com/images/cppblog_com/len/WindowsLiveWriter/f5af5ed389e0_9458/p3_thumb.jpg" width="318" border="0" />
		<p>设置完好后,你就可以编译运行了,一个什么都不干的丑陋的windows窗体出现你面前了.若编译不通过,缺少某些头文件的话,查看出错信息,然后用windows查找对应头文件路径,再设置.若连接出错,很可能在设置库文件出错了,同理,设置相应的库文件路径.程序代码很简单,定义一个windows窗体指针,然后创建窗体和定义窗体标题,接着就是显示了,和gtk主循环了.</p>
		<img height="346" alt="p4_2.jpg" src="http://www.cppblog.com/images/cppblog_com/len/WindowsLiveWriter/f5af5ed389e0_9458/p4_2.jpg" width="433" border="0" />
		<br />
		<h3>让GTK+有点知觉</h3>
		<p>或许你发现了，点击窗体中的"X"关闭按钮，虽然窗体消失了，但是黑色的命令窗口还在，发现＂任务管理器＂中程序还在运行着．对，它并没有像你想的那样，在点击"X"关闭按钮结束掉程序．这些是需要你告诉程序该怎么做的．我们需要在代码中添加相应的事件处理函数. 新增加了destroy函数，它就是事件处理函数，在gtk也可叫做信号处理函数，因为gtk中用信号来进行事件的通知．destory函数的形参是有约定写法的，这个大家可以参见具体的手册．在主程序中，还需要将这一函数与具体的信号进行关联，见第17行．</p>
		<p>在完成这些工作后，你再点击"X"按钮时，会发现"命令行窗口"会出现"按任意键继续"，程序是真正地退出了.</p>
		<pre class="code">
/* file gtk.c */
#include 
static void destroy( GtkWidget *widget,
                    gpointer   data )
{
    gtk_main_quit ();            //退出gtk主循环
}

int main(int argc, char* argv[])
{

    GtkWidget* window;
    gtk_init (&amp;argc, &amp;argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    
    gtk_window_set_title (GTK_WINDOW (window), "Hello World");
    g_signal_connect (G_OBJECT (window), "destroy",    G_CALLBACK (destroy), NULL);

    gtk_widget_show (window);
    gtk_main();


    return 0;
}
</pre>
		<h3>让黑黑的命令行窗口消失</h3>
		<p>有些人会对这个gtk应用程序总会伴随着黑黑的命令行窗口感到不爽，包括我自己在内．不知道gtk程序在gnome下也是这样，我没有试过，不知道．现在我就使用windows平台的特定方法，来让它消失吧，就是使用WinMain做为主函数．这方法或许不是最正规的做法,但我看来却有效．如果要考虑到跨平台的话，可以用条件编译处理下．</p>
		<pre class="code">
/* file gtk.c */
#include <windows.h>
#include 
static void destroy( GtkWidget *widget,
                    gpointer   data )
{
    gtk_main_quit ();            //退出gtk主循环
}

int WinMain(HINSTANCE hInstance,
            HINSTANCE hPrevInstance,
            LPSTR lpCmdLine,
            int nCmdShow
            )
{
    //为应付gtk_init所需要的参数
    int argc=1;    
    char* commandLine={"gtkApplication"};
    char** argv = &amp;commandLine;

    GtkWidget* window;
    gtk_init (&amp;argc, &amp;argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    
    gtk_window_set_title (GTK_WINDOW (window), "Hello World");
    g_signal_connect (G_OBJECT (window), "destroy",    G_CALLBACK (destroy), NULL);

    gtk_widget_show (window);
    gtk_main();


    return 0;
}

</windows.h></pre>
		<p>
好啦，现在再编译运行，命令行窗口没有了．你会发现真正的win32窗体了．但是在开发中，我建议用gtk时，还是让命令行窗口显示出来，因为gtk在出错时，会把一些有用的信息打到命令行窗口中，这对你的帮助会很大．</p>
<img src ="http://www.cppblog.com/len/aggbug/45190.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/len/" target="_blank">len</a> 2008-03-23 15:18 <a href="http://www.cppblog.com/len/archive/2008/03/23/45190.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>PC-lint</title><link>http://www.cppblog.com/len/archive/2008/01/30/42222.html</link><dc:creator>len</dc:creator><author>len</author><pubDate>Wed, 30 Jan 2008 07:18:00 GMT</pubDate><guid>http://www.cppblog.com/len/archive/2008/01/30/42222.html</guid><wfw:comment>http://www.cppblog.com/len/comments/42222.html</wfw:comment><comments>http://www.cppblog.com/len/archive/2008/01/30/42222.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/len/comments/commentRss/42222.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/len/services/trackbacks/42222.html</trackback:ping><description><![CDATA[
		<p>PC-lint for C/C++是由Gimpel软件公司于1985年开发的代码静态分析工具,它能有效地发现程序语法错误、潜在的错误隐患、不合理的编程习惯等。</p>
		<p>FlexeLint for C/C++是在PC_lint在windows平台获得成功后，同样由Gimpel公司开发的，以源代码形式发布的，在Unix/Linux平台上的静态代码分析工具。</p>
		<p>本文主要介绍PC-lint的安装与配置，因此是在windows平台上进行讨论。</p>
		<p>PC-lint支持几乎所有流行的编译器和IDE环境，可能因为其发展历史和面对专业程序员群体的原因，它是以命令行加配置文件的形式进行使用，所以其使用习惯跟现在常见的windows软件不同。</p>
		<p>现以PC-lint与VS2005进行集成来说明：</p>
		<p>将PC-lint释放到某一目录下,如:D:\Program Files\pclint.将新建一std.lnt文件在主目录中，并将添加上以下的内容</p>
		<pre class="code">au-sm.lnt
co-msc80.lnt
lib-mfc.lnt
lib-stl.lnt
lib-w32.lnt
lib-wnt.lnt
lib-atl.lnt
options.lnt  -si4 -sp4

-i "C:\Program Files\Microsoft Visual Studio 8\VC\include"
-i "C:\Program Files\Microsoft Visual Studio 8\VC\atlmfc\include"
-i "C:\Program Files\Microsoft Visual Studio 8\VC\PlatformSDK\include"
-i "C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\include"
</pre>
		<p>注意：-i后面为相应的vc头文件目录路径,而一系列xxx.lnt是语法配置规则，决定了按什么规则进行检查，以后可以根据需要进行增减. 
写在配置文件中相应的的xxx.lnt从lnt子目录中，拷贝到主目录中．（这一步很重要)
在vs2005中的工具-&gt;外部工具中，点击＂添加",新建一个外部工具.标题可以任意，可取(pc_lint)；命令为：D:\Program Files\pclint\LINT-NT.EXE；参数为:-i"D:\Program Files\pclint" std.lnt "$(ItemFileName)$(ItemExt)";初始目录为：$(ItemDir)，并将下面的＂使用输出窗口＂勾选上.
接下来，就可以写一段程序，在工具菜单中选择pc_lint 来进行检查了．如果你编写的程序有不符合定义的规范，则会在输出窗口中出现相关的信息.</p>
<img src ="http://www.cppblog.com/len/aggbug/42222.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/len/" target="_blank">len</a> 2008-01-30 15:18 <a href="http://www.cppblog.com/len/archive/2008/01/30/42222.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>