Shell's Home

Jan 26, 2007 - 1 minute read - Comments

基础类

设计程序的时候,往往觉得怎么这么困难,基础类为什么不多提供些能力。现在风水轮流转,我开始设计基础类了。 站着说话不腰疼,设计基础类才发现基础类这东西真不是人做的。调用上讲究非常多。要返回引用呢?还是值?是需要const呢,还是不能const。返回的时候是一次拷贝构造呢?还是两次。算子需要不需要设计成friend,重复代码能不能消除。着重效率还是安全性,线程安全不安全。这些问题真是活活逼死人啊。 现在正在设计实现一下几个类,有兴趣的可以一起来研究。 LargeInteger 超大整数实现 计算RSA的时候很有用,考虑在内部实现一些有用的算法 Matrix模板类,容器类 矩阵实现 设计的时候就考虑到内部容纳的不一定是数据,也可能是字符串或者超大数 vector2D 两维矢量 专门针对平面计算优化 Line 两维线 专门针对平面计算优化

Jan 26, 2007 - 1 minute read - Comments

关于Java和C++的一点争论

不知道为什么,大家好像都喜欢争来争去。关于Java和C++的优劣不知道听了多少。碰巧我两者都会,怎么说也算是公平了吧。我就大着胆子,比较下两者的情况。 对程序而言,速度不是最终要素。否则我们都应该去用汇编不是?一个程序有六个特性,易学,易用,安全,高效,可变,成本低。然而他们一般都是两两冲突的,好学了,就不好用。强大了,就不好学。安全了,自然要执行很多检查,高效了,自然不安全。针对某个平添优化了,可变性就很差,又不能移植,又不好修改。成本低了,自然什么都差。 往往我们写企业应用的时候,都看重可变,低成本,安全,易用。高效呢?企业有钱买大服务器啊,这样的话效率差的不是太多也能接受。易学呢?企业有钱搞推广培训啊,只要有什么功能就加什么功能,不用考虑学不会。写用户应用的时候,则是看重安全,高效,然后易学易用里面要占据一样。可变呢?用户应用有多大?不行重写一个。低成本呢?这就是比较吊诡的事情了,没有啥经济效益的用户程序,往往是写起来最不怕费时最不管经济效益的。 首先从性能角度来说。也许C++程序员说到这里就得意了,不过先别高兴。如果单论速度,汇编语言还在C之上呢。现在网络上很多人讲C优化好了比汇编快,Java优化好了比C快。听听都要笑掉大牙的。C再怎么快,完成同样的步骤,都需要这些汇编代码。Java再怎么快,完成一个动作,底下C代码也不会少的。所谓Java优化好了能快过C,不过是一个Java高手一门心思搞优化,加上碰到一个C语言白痴而已。 我们先不讨论上面问题,就一般Java程序员和C++程序员而言(注意为啥我没说是C程序员,因为能自称纯C程序员的人要么非常精通语言,不会使用C++特性,要么就根本是个白痴),Java程序员编写出的代码效率比C++大约慢5倍上下。这个数据是我个人写两个程序,一个运算,一个读取处理,对比出来的。都是没有优化的代码。经过极端的优化后,C++的代码我大概提高了4-8倍的速度。可惜我不是个很好的Java程序员,Java代码的速度大概提升了一倍还不足。就是说,最终C++代码比Java快了将近15倍。 但是C++程序员们先别乐,首先我Java语言并不好,这还不是最终的速度比。其次我牺牲了C++的很多特性。运算上几乎就是在写汇编了,接口都直接用了WIN32SDKAPI。没有移植性,没有可维护性,还需要特殊的技巧,怎么想都是牺牲重大。如果要真的这么追求速度,相信汇编会是更好的选择。我们在速度相差5倍左右的情况下就可以使用C++而不用汇编,为什么不能在速度相差5倍的情况下选择Java呢? 然后我们再看性能的另外一个方面,存储管理。说简单点,就是外围设备吞吐管理和内存管理。这方面上C++也是远远超越了Java。不需要的内存就不要,不必须的吞吐就不吐。C++是门培养人的语言,没有很好的功底是无法驾驭的。C++是门程序员负责一切的语言,任何错误都是程序员的错误。然而对于Java来说,就不必处理复杂而没有意义的内存管理了。假设一个Java程序员需要传一个对象给子函数,他只要传递就可以了。然而如果C++程序员直接传递,那么就会出现参数拷贝过程。不但效率差,还可能出现错误。单单一个参数传递,就有三种方式。传值,传址,引用。又分成四类,静态动态,常量非常量。交叉起来,总共是12种情况,需要量材选用。如此烦琐的管理方式,我们可以想想对于内存来说是很有好处的。嵌入系统中大型程序设计绝对是C的天下。然而这么困难的使用方式,需要多大的人力成本才能做到呢?这明显的违反了低成本的原则。 Java的内存管理从C++的角度看绝对是具有瑕疵的,内存释放了不管,直到没有空间了才收集。然而很多C++程序高手在特殊情况下,会重载operator new算子。其中的行为就很类似这个,Java只是将特殊情况下的应用放到了一般情况。这样对于速度的后果就是,很多的缓存会被持续的从物理内存中挤压出去,导致磁盘吞吐效率降低。对于Java程序,我猜测提升效率的瓶颈将会在和系统交互以取得最佳的垃圾收集时间上。 下面的论题就可能是C++程序员所不高兴看到的了,安全性和可变性。 就语言来说,用户输入的检查这种安全性是一点意义都没有的。我们所说的基本包括几个方面,非正常用法安全,线程安全,异常过程安全。就语言来说,分为了解释型和编译型。那么怎么区分呢?我这里有个很简单的方法,如果程序本身能被本身修改,就是编译型的,否则是解释型。注意,不是让你修改了磁盘上的源码再运行。这个能力赋予了C++强大的功能,例如修改游戏,检查病毒,都需要这个能力。但是不可否认的,比起无法修改自身的程序,安全性就差太多了。线程安全性上讲,Java的所有对象都是系统管理的,也就很容易的可以管理互斥。用过C++的都知道,C++自身是没有互斥的,全靠系统的函数库或者第三方库支撑。好用不好用不说,无法移植是一定的!最后是异常过程安全,C++的异常过程是很恐怖的。关键在于C++的异常传递有三种办法,传值,传址,引用。而且在异常过程中还又涉及回了内存管理啥的。天啊,要是异常都不能专心处理异常,我还要异常干嘛? 至于可变性,那就更别说了。连处理异常都要小心内存泄露的家伙,你指望在修改代码的时候轻松到哪里去?C++是具备了强大的可变性,然而处于效率考虑,很多增强可变性的东西是选项的。例如RTTI,居然要开编译器选项的说。需要使用可变性就要牺牲性能,而且还要你小心的使用。如果使用不小心,抱歉,你又陷入效率和安全的问题里面去了。 如果你有很好的系统功底,准备往计算机领域发展(注意不是计算领域)。那么C++是门很好的语言,只是非常费时而已。而且建议你学C#,VB或者Java作为第二语言,选择Bash或者Perl作为第三语言。这样在处理问题上可以事半功倍。如果你打算增加自己的计算机能力,方便日常的电脑使用,而不准备深入学习这个领域。那么只学C#,VB或者Java就足够了。 最后要提到的是易学和易用,这两个特性都是软件设计所赋予的。也就是说,即使是以简单著称的VB.NET,也可以写很好用的程序。以复杂著称的汇编,也可以写很友好的界面。这就不在本文的论题以内了。

Jan 25, 2007 - 1 minute read - Comments

TTS杂论

近两天按照公司要求,试用了下TTS。现在能看到的TTS主要有4个,MS TTS,IBM TTS, FreeTTS, Festival.多少都有点问题啦。 Festival只支持英语,西班牙语,威尔士语。FreeTTS居然是Java程序(不过想想也是,要是Java没款TTS产品才奇怪了呢)。MS TTS倒是简单好用,可惜效果太差。微软研究院放出的线上版本又不知道怎么购买。IBM TTS很贵,而且还只有服务器版本。所以这里就挑用过的说(其实就是MS TTS)。 MS TTS很简单,安装,然后核心组件就会在系统内注册到COM组件。然后按照COM组件来调用就是了。使用哪种发音库可以在控制面板里面修改,也可以用发音语法标记来定制。可以解析多种情况,例如标准化存储,文件,字符串。可以输出到特定设备,例如WAV文件。基本就这样。

Jan 19, 2007 - 2 minute read - Comments

多线程同步说

先给大家看个恐怖东西. class TIFF2JPEG { public: static void newInstance (tstring & strFilepath, HGLOBAL hGlobal); static void waitObject (); protected: static ULONG_PTR initInstance (); static DWORD WINAPI ThreadProc (LPVOID lpParameter); TIFF2JPEG (); TIFF2JPEG (tstring & str, HGLOBAL hg); int Translate (); static ULONG_PTR gdiplusToken; tstring strFilepath; HGLOBAL hGlobal; DWORD dwSize; }; int ImageInMemory = 0; deque < TIFF2JPEG * >t2j_list; CRITICAL_SECTION CriticalSection_t2j_list; CLSID GifCodec; bool bThreadDelete = 0; vector < HANDLE > vThreads; TIFF2JPEG::TIFF2JPEG () { } TIFF2JPEG::TIFF2JPEG (tstring & str, HGLOBAL hg):strFilepath (str) { hGlobal = hg; dwSize = GlobalSize (hGlobal); return; } ULONG_PTR TIFF2JPEG::initInstance () { int i; HANDLE hThread; GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; GdiplusStartup (&gdiplusToken, &gdiplusStartupInput, NULL); GetCodecClsid (L"image/jpeg", &GifCodec); InitializeCriticalSection (&CriticalSection_t2j_list); for (i = 0; i < MAX_THREAD; i++) { hThread = CreateThread (NULL, 0, TIFF2JPEG::ThreadProc, NULL, 0, NULL); SetThreadPriority (hThread, THREAD_PRIORITY_IDLE); vThreads.

Jan 17, 2007 - 1 minute read - Comments

全局初始化

大家写代码的时候,假如写了一个组件,自身需要开一个线程的。那么多数是在初始化代码中直接写开线程的。如果要求这个类不在初始化代码中添加函数,怎么做? MSDN中有个例子,不是解决这个问题的,但是可以用。 class DialogWindow{ public: static short GetTextHeight(){ return 1; }; private: static short nTextHeight; }; short DialogWindow :: nTextHeight = GetTextHeight(); int main(){} 在这里,把GetTextHeight里面添加上你需要的代码就可以了。 但是注意,这些代码的运行时间要早于main(准确的说,是在wmainCRTStartup里面运行的_c_init函数)。所以很多自然而然就有的初始化没做,你自己在main里面调用的初始化(例如WSAStartup啦,GDI+初始化啦)更是没做。所以必须严格测试。

Jan 16, 2007 - 3 minute read - Comments

KMP

KMP是一个给人捧滥了的算法,其实单看同时有三个发明人这点,就知道这个算法是自古华山一条路,没别的想法的。KMP的算法每步虽然难想但是自然有道理,没有别的方法的。不像排序算法,一嘟噜的算法还没完。根据各种侧重点不同有不同的算法可用。 KMP的比较算法核心在于不回朔。我们先看一个正常的例子。 SSSSSSSSSSSSSS TTTTT i=0, j=1,2,3… TTTTT i=1, j=1,2,3… TTTTT i=2, j=1,2,3… 我们用T去匹配S,先对齐T和S的头部,然后对比T和S。如果T的范围内,TS内容一致,则匹配成功。不成功的话就将T向后移动一个字符。再次匹配T范围内TS的内容是否一致。ij的范围最大会在0<=i<=“j KMP算法的核心在于,如果T范围内TS的内容不一致,那么T向后移动不是一个字符,而是多个。而当前比较位置永远不向前移动。如果您写出来的算法当前比较位置向前移动了,那么肯定是写错了。 我们假定T范围内第i个字符匹配失败(0<=i 为什么能移动一个以上呢?Next[i]怎么确定呢?我们看一个抽象的例子:M代表匹配,N不匹配,U不知道。 MMMMMNUUUUU TTTTTTTTTTTTTTT TTTTTTTTTTTTTTT 我们可以看到,向后移动字符的时候,T自身有重合的部分。这时候有三种状况,我们先假定一种重合的状况。假定T向后移动了一些字符,P代表当前比较位置。在这个位置上TS出现了不匹配。 ..MMMNUUU.. ..TTTTTPTTTT.. ..MMNTTTTTT.. 在这个T向后移动一些距离后,在当前比较位置前已经出现了自身和自身不匹配的状况。根据上面我们知道,当前位置以前的T和S是匹配的。那么就是说,移动了这些字符后。T当前的位置上不可能取得匹配。我们称这种情况为这个位置的自身完全不匹配。 然后是另外一种匹配状况,符号和上面一致。 ..MMMNUUU.. ..TTTTTPTTTT.. ..MMMNTTTT.. 这个时候,T在当前匹配位置上自身不匹配,前面位置都匹配。同理可以知道,T在滑移到这个位置后可能取得匹配。我们称这种情况为这个位置的自身部分不匹配。 最后一种匹配情况是。 ..MMMNUUU.. ..TTTTTPTTTT.. ..MMMMUUU.. 这个时候,T在在前面和当前位置都匹配。我们知道前面是匹配的,然而当前既然已经被证明了和S不匹配。那么滑移这些位置后,新的位置上T也不可能和S取得匹配。我们称这种情况为这个位置的自身完全匹配。 然后我们可以回头看看比较过程了。当我们对齐TS,然后进行比较的过程中。出现了不匹配。 注意一个问题,我们回朔是为了知道移动后的T是否仍旧匹配。所以如果我们知道T匹配不匹配,就不用回朔。 这个时候我们不移动一次T,然后回朔。而是将T滑移一下,先滑移1位好了。假设出现了当前比较位置的自身完全不匹配或者自身完全匹配,那么滑动1位肯定也无法获得一个有效的匹配。那么就继续滑动,直到出现了自身部分不匹配,或者T已经完全的滑动到了当前位置的后面。这时候T的位置才是有可能获得匹配的位置,其余的位置就没必要浪费时间了。 也就是说,滑移距离Next[i]是i这个位置上滑动距离逐渐增加的时候,首次出现自身部分不匹配情况的位置。如果这情况不出现,那么就设定为<1的值。操作上将整个T滑动到当前位置的后面,并且从下一个位置开始比较起。 然后附上初始版的代码和比较过程。 int *cul_next (char *lpTpl) { unsigned int i = 0, j = 0, l = 0; int *next; l = strlen (lpTpl); next = new int[l]; memset (next, 0, l * sizeof (int));

Jan 15, 2007 - 1 minute read - Comments

查找重复文件

算是介绍一个奇淫巧技吧。查找重复的文件,这个应该有很多软件都可以做的。不过在Linux里面,利用系统工具,一句语句查找应该就比较少见了。 $find . -name "*" -type f -size +0 -exec md5sum {} ; | sort | uniq -d -w 32 原理是这样的,先用find查找当前所有文件。我们加上限定类型必须是文件,目录不要。限定大小不为0,空文件不要。然后对所有满足条件的执行md5sum,获得md5和文件的列表。然后排序,再针对md5的部分做唯一限定。就得到了所有md5相同的文件的列表。 问题是,这时候我们得到的还只是一堆重复的文件的md5。我们可以把以上步骤拆开来获得完整的输出。 $find . -name "*" -type f -size +0 -exec md5sum {} ; | sort > file_md5 $cat file_md5 | uniq -d -w 32 $grep "..." file_md5 相信大家都看出是怎么回事情了,就不赘言了。 Windows下以前我总是执行不出,原因在于要这么写。 find . -type f -size +0 -exec md5sum {} ; | sort > report.txt 差一个转义,因为不需要。 总结一下,我们可以用一个脚本来处理这些问题。 --------------------------fine_rep------------------------------ find $1 -type f -size +0 -exec md5sum {} ; | sort > "file_md5" cat "file_md5" | uniq -d -w 32 | cut -d" " -f1 | while read x do grep "$x" file_md5 echo "" done echo "done" rm "file_md5" ------------------------------------------------------------

Jan 13, 2007 - 1 minute read - Comments

构造过程

在进入正题前,我们首先回顾下我们的基础C++常识。如果调用某个对象的某个方法,那么会调用到什么? 一般来说,如果这个方法是普通函数,则按照这个对象的声明类型去调用。就好像(&obj)->obj_type::function(param…);。而如果是虚函数,则是按照这个对象的构造类型去调用。就好像(&obj)->((&obj)->_v_ptr[n])(param…);。或者说简单点,一个是按照这个类看起来像是哪个类来调用,一个是按照这个类实际是哪个类来调用。 那么,如果在一个基类的某个成员里面调用这个类的另外一个虚函数,调用的是哪个呢? 任何正常人来说,都应该说是,按照这个类的构造类型来确定。基本上没错,不过有一个函数例外。 那么先看一个问题。 class B { public: B(){test ();} virtual void test (){printf ("parentn");} void out (){test ();} }; class D:public B { public: virtual void test (){printf ("childn");} }; int _tmain(int argc, _TCHAR* argv[]){ D d; d.out(); return 0; } 想像下输出,再运行下。想想为什么,再看下面。想通了就不用看了。 ———————我是无敌的分割线———————– C++标准规定,任何一个带有虚函数的类都有一个_v_ptr成员,这个成员必须存放在这个类内存地址中头部。这个成员指向了这个类的虚函数表。于是,调用虚函数的时候,我们首先确定这个是虚函数。(按照这个逻辑,如果父类不声明为虚函数,子类重载为虚函数,还是没用的)然后,我们确定这是第几个虚函数(严格来说,这并不符合面对对象的设计规范,应该是按照函数名字查表的,_v_ptr也不应该仅仅指向虚函数表,而应该是类形态表)。最后,我们去虚函数表中取得入口地址进行调用。 那么为什么在构造函数中调用就无法调用子类的虚函数呢?问题在于_v_ptr的初始化时间上。某个类的构造函数启动前,这个类的_v_ptr才能完成初始化。如果是多重继承,那么首先调用最初类的,然后是次类的,最后是子类的。_v_ptr首先指向基类的,再是继承类,最后是子类。我们在父类构造函数中,_v_ptr指针还指向了父类的虚函数表,所以调用不到子类的虚函数。 其实我们可以这么说,构造函数以前,子类不存在。

Jan 12, 2007 - 1 minute read - Comments

IDE

现在真是深深的为IDE所苦啊。 基本来说,现在用的IDE有几种,eclipse,MSVC,Anjuta,MingStudio,Code::Blocks。不过都有毛病。 eclipse好用的很,功能齐全,插件多,支持多,跨平台。可惜开发Java是本行,C就稍微有点小毛病,而且消耗资源太大。MSVC也是功能强大,可惜Linux下面没法用,而且不支持CVS。Anjuta只是Linux下面的开发程序,而且使用了Automake和Autoconf。MingStudio不开源,Code::Blocks编译困难。 不过想想也是,IDE都又好用又免费了,M$要$哪个去? 其中唯一好用点的也就是Code::Blocks了。前两天看一个论坛上说Code::Blocks是针对单语言的,所以要用Anjuta。我立刻喷笑。说起来一个人能会多少语言呢?三种?五种?要真的是多语言,不如用Eclipse或者Emacs。Eclipse支持Java/C++/Php,Emacs配置好了啥都支持。问题是支持多了,效果就差了。Eclipse在支持C++上就和MSVC没的比。 要比较一个IDE,基本来说是比较三个方面,自动写作,生成和版本管理,统一化测试/调试。 自动写作有四个主要方面,自动完成,符号提示,参数提示,自动格式化。说明白点,自动完成,就是使用某个范围的某个东西,例如变量或者方法。在写好范围(例如对象名或者类名)后会提示这个东西的名字。好比写了::Messa就基本可以自动给出::MessageBox。符号提示则更加智能些,在写作的时候自动判断当前最可能使用的对象并且给出提示。参数提示是指调用函数的时候提示应该传入的参数类型和个数,当然也有自动将最可能的变量传入的例子。自动格式化,就是乱糟糟的代码自动格式整齐。针对C++来说,MSVC可以自动完成,符号提示,参数提示,外挂插件可以自动格式化。Eclipse可以自动完成,参数提示(这就是那个自动填写最可能参数的例子),外挂插件可以自动格式化。Anjuta和Code::Blocks没用过,不过Code::Blocks可以外挂插件格式化。 生成和版本管理来说,MSVC,Code::Blocks和Eclipse用的是自身的格式,脱离了IDE基本就没用了。Anjuta用的是Autoconf和Automake,即使没有IDE也可以编译。便于跨语言和发行原码包。 统一化测试/调试来说,基本都有行调试的功能,不过都不带测试工具的。

Jan 11, 2007 - 1 minute read - Comments

用户和软件制造商的博弈

作为用户来说,软件越便宜越好。制造商来说,越贵越好。 软件的价格和很多因素相关,生产基础成本,市场竞争,应用市场,技术含量。不过我们今天分析三种因素,服务,技术和市场。 软件的价格中,有很大一部分是用于服务的。大致上包括客户服务费用,安全保证费用(例如出现产品造成客户损失等等),软件升级和维护费用。免费软件和收费软件在这方面区别最大,收费软件自然可以做好(应该说是必须做好)服务,免费软件也可以做好服务。但是免费软件既然是免费的,让我免费写了大家用我不介意,反正没人用也是浪费,有人用还可扬名。但是还要提供服务,恐怕没人乐意了。所以免费软件的服务基本都是空白,或者是收费的(例如apache的文档)。这个因素基本和我们今天讨论的主题不相关,只是讨厌于某些厂商的服务质量有感而发。 市场的领域和含义非常复杂,大致上领域包括了某个产品理论可以用于的领域和当前用于的领域。理论上说记事本可以用于超大的程序开发,实际上你看到有人在用记事本写代码吗?含义的话,市场不仅仅是一个可用的范围,还应当包括所有用户和所有竞争对手,以及所有人的互相关系和当前状态。其实这已经是非常简化的状况了,软件的市场远远没有传统行业来的大,但是复杂程度却尤有甚之。美国的一个农民可能出售牛肉到日本,中国的一个农民也可能出售牛肉到日本。但是这两个农民互相间不认识,他们不构成直接的竞争动力。他们的代理出售者可能竞价,但是他们本人基本不可能调整自己的产品(当然,我也想像不出来牛肉可以怎么调整——除去中国某些恶心商人的手段外)。然而美国的一个公司卖软件到日本,中国公司也卖,他们一定认识,而且会根据对手行为和当前处境,以及客户状态来调整他们的产品。 最后一个是竞争的关键,技术。这里讨论的是广义上的技术,即抛却含量,纯粹从跑马圈地的角度来讲。如果讲技术的发展性,那最好大家公开所有技术内幕,不过看来不可能的。 技术的意义在于做别人不能做的事情。windows的进程管理器不能显示用户加载模块,sysinternals的就可以。这就是技术。有技术就有仿制。为了保持技术,一般有两种方法。一种是持续研发,成本高,但是优势十足。还有就是专利,恶心人的无奈东西。 如果说技术只是单纯能或者不能的问题,结合到市场上就有复杂的变化。最重要的一种就是利用技术特性来占领市场。如果某个产品是开放的,例如开放标准,出售代码。就可能出现很多不准确的免费仿制品。这些仿制品的大量应用奠定了这个产品的基础,使得产品具备非常大的市场和价值。然而市场,准确的说是用户,是具备产品粘着度的。也就是说,如果喜欢一个产品,就会一直使用这个产品。如果一个产品不具备粘着度,那就完全的没有价值了。因为他的用户随时都会转变为别人的。培育市场,就是培育大量的用户,并且使得他们具备高的粘着度。技术上说,存在这么一种情况,专利A,开放标准,出售研发代码(SDK)。专利B,封闭标准,出售研发代码,但是兼容专利A。那么专利B的产品会给客户一种导向,就是B比A更好。如果价格一样的话,我们不难想像客户的选择。这样的话A不仅仅是流失客户的问题,而且在后续产品上,用户会有惊人的粘着度。一直都是B专利兼容A专利,一直被压了打。 理论上这样会导致大家不愿意在核心格式上开放标准,然而标准的开放会带来非常广阔的兼容性好处。例如著名的开放标准XML,从技术角度讲几乎就没有什么难以理解的高级技术。然而这个标准本身却是伟大的发明。所以比较流行的方式应当是授权标准,标准是免费的,但是是授权的。如果在此上面衍生变化必须得到标准化委员会的认可,然而其中还是有很大问题的。例如微软就变更了java的标准,并且在事实上(虽然从来不承认)变更了html的标准。IE可以浏览标准的html,但是标准的html浏览器却不能浏览IE的格式。由此可以看出各个生产厂商在专利上的竞争方式。 作为厂商来讲,最好的运作模式是没实力兼容标准,这样用户觉得你功能强大。有实力就修改标准,这样可以养成用户粘着度。然而在用户看来,最好选择仅仅使用最开放最便宜的标准的软件。虽然这样会在使用中造成不便。然而却杜绝了大厂商篡改标准圈地的可能,在用户和软件公司博弈的时候获得更大的优势。