huaxiazhihuo

 

消息发送VS设计模式 C++沉思录的一练习题的另解

     摘要:         缘起,看到一遍文章,题材取自于《C++沉思录》,综合利用好几种设计模式,并且结合所谓的面向对象的技巧,然后洋洋自得,并且在最后,还反问:“有更好的解决方案吗?”。本座暗叹,又一个设计模式的毒害者。以下,就提出另一种解决方案。      &...  阅读全文

posted @ 2012-06-12 17:16 华夏之火 阅读(2541) | 评论 (8)编辑 收藏

基于堆栈上的字符串实现

           C++中,由于字符串一开始并非内置的类型,于是,史前时代,江湖上出现了种种的字符串,各有各的优点,但自然,也各有各的缺陷,群雄割据,天下大乱,民不聊生。大伙儿盼星星,盼月亮,盼着真正的字符串能出来一统江湖,好不容易,等到1998C++标准出来了,官方的字符串string终于露面了,自然,它并不是语言内置支持的,而是在STL库中。当然,字符串这东西,就算要在C++编译器的层面上给予支持,其实也很不容易。可是,这个官方的string,单纯它复杂的模板定义和那一大陀函数成员,就足以吓退众多意志不坚定之人。好了,好不容易,鼓起勇气,再仔细瞅瞅string中的东西,发现它也不是很美妙,既不强大,部分函数的功能存在重复又或者STL的算法中已有提供,更要命的是,效率也不怎么高。总之,不能说string的设计不如史前的各种stringS,但也强不到那里去。当然,官方的总比非官方的更具权威,但每次使用string时,想到它背地里除了正常字符串操作之外,还可能做了各种的低效的内存分配释放的操作,又或者线程安全,又或者引用计数,内心就一直惴惴不安。于是,宁愿一再小心翼翼地用着字符数组。但是,用得多了,也很郁闷,字符数组自然高效、灵活,但总是要千编一律地一再写着容易出错的代码,我脆弱的心灵终于头晕眼花了。于是,我决定按照自己的意愿写一个字符串,不敢欲与群雄争锋,只是,为了能够在自己的代码中,替换字符数组。我对它的要求是,字符数组能做到的事情,它也要做到,并且,效率上,绝不妥协。

          俗话说,过早的优化是万恶之源。但在此,在设计本家字符串时,一开始,就要考虑到效率的细节上去了。首先,它要支持堆栈变量的形式,不要它进行内存的分配释放操作,就好像堆栈上的字符数组那样。咦,好像很神奇,其实,只要想到TR1中那个经典的array,通过使用神奇的模板技术,就有办法做到了。所以,此字符串的使用好比这样子,CStackString <MAX_PATH> sFile,暂时假定这个字符串的名字叫CStackString

          但是,使用模板之后,字符串的字符数组的长度只要不一样,它们就都属于不同类型变量,并且之间还都不兼容呢,虽然它们都是字符串。此外,还会编译器还将生产出一堆重复的代码。这无论如何,都不能忍受。于是,自然而然,就想到了继承。CStackString模板类继承于非模板的mybasestringmybasestring中实现了CStackString的各种各样的操作,而CStackString只要仿照array那样子,定义好自己的数据成员即可。嗯,还是看看代码,马上就明白到底是怎么一回事了。


class CMyBaseString
{
public:
    typedef size_t size_type;
    typedef 
char *pointer;
    typedef 
const char *const_pointer;
    typedef CMyBaseString _Myt;

public:
    
char operator[](size_type nPos)const
    
{
        assert(nPos 
< m_nLen);
        
return m_str[nPos];
    }


    const_pointer c_str()
const return m_str; }

    const_pointer right(size_type nLen)
const
    
{
        assert(nLen 
< m_nLen);
        
return m_str+m_nLen-nLen;
    }


    
int compare(const_pointer str)const
    
{
        
return strcmp(m_str, str);
    }


    _Myt
& assign(const char* str)
    
{
        m_nLen 
= strlen(str);
        assert(m_nLen 
< m_nBuffSize);
        strcpy(m_str, str);
        
return *this;
    }


    _Myt
& append(const_pointer str)
    
{
        size_type nLen 
= strlen(str);
        assert(m_nLen 
+ nLen < m_nBuffSize);
        strcpy(m_str
+m_nLen, str);
        m_nLen 
+= nLen;
        
return *this;
    }


    _Myt
& format(const_pointer sFormat,  )
    
{
        va_list argList;
        va_start( argList, sFormat );
        m_nLen 
= vsprintf(m_str, sFormat, argList);
        va_end( argList );
        assert(m_nLen 
< m_nBuffSize);
        
return *this;
    }


    
//.

protected:
    CMyBaseString(pointer sBuf, size_type nBuffSize)
    
{
        m_nBuffSize 
= nBuffSize;
        m_nLen 
= 0;
        m_str 
= sBuf;
        m_str[
0= 0;
    }


private:
    size_type m_nBuffSize;
    size_type m_nLen;
    pointer m_str;
}
;

template
<size_t _size>
class CStackString : public CMyBaseString
{
public:
    CStackString(
const char* str) : CMyBaseString(m_sMine, _size) { assign(str);}
    CStackString() : CMyBaseString(m_sMine, _size) 
{}

private:
    
char m_sMine[_size];
}
;
int main()
{
    CStackString
<20> sTest("hello");
    cout 
<< sTest.c_str() << endl;
    cout 
<< sTest.right(3<< endl;
    
return 0;
}

          于是通过基类mybasestring,各种不同类型的template CStackString就又联系在一块了。Mybasestring可看成定义了数据接口的基类,其子类的头部数据必须与它保持一致,嗯,很好。然后,在mybasestring中实现的各种功能,都可以用在mystring身上了,而CStackString中无须实现任何功能,它只负责在堆栈上分配内存。所以,mybasestring不仅可以用在堆栈上,还能用于堆上,只要再继续定义一个能在堆上分配内存的mybasestring的子类即可,然后都能相容于堆栈上的CStackString字符串。

          ……。 经过一番努力,这个字符串类几乎包含了字符数组的一切基本功能,基本上可代替字符数组了。为了能够用到STL中的各种算法,再在其上增加一些返回迭代器的函数,好比beginendrbeginrend,它们都很容易实现。还有,为了使用起来更加友好方便更具效率,貌似应该再实现一些全局操作符的重载运算;……;好了,打住。如果打算将这个字符串很好地融入到STL中,需要做出更多的努力。原本只打算代替字符数组而已。代码在VC2005以上版本编译时,会出现一些警告,可以用#pregma的指令将其disable掉,或者使用其中的所谓的安全字符串操作函数。

          好不容易,终于就实现了一个基于堆栈上的字符串,它是窄字符的。别忘了,还有宽字符的字符串呢。这也没什么,只须将mybasestring重命名为CMyBaseStringACStackString改为CStackStringA。然后再分别实现与CMyBaseStringACStackStringA同样接口的CMyBaseStringWCStackStringW。然后,再针对UNICODETypedefdefined一对CMyBaseStringTCStackStringT。在这里,并不想模仿STL中的stringMFC中的CString那样子,template一个basic_stringsimple_string),然后分别进行模板特化,近来越看越觉得这种模板特化的方式相当恶心,只是将代码搞得更加复杂,却没带来多大的好处。

          相比于其他的字符串实现,这个mybasestring不过是将内存分配拱手让人罢了。这样一来,就带来一些新的问题。首先,它要假设其子类给它分配了足够的内存,不过,在C++传统,经常假设用户分配了足够的内存;然后,因为脱离了内存管理,有一些功能自然也就无法实现出来了,C的字符串函数也还不是这样,当缓冲溢出时,该崩溃就还得崩溃。

          再次向C++的无所不能顶礼膜拜。C++,你是电,你是光, 你是唯一的神话, 我只爱你,You are my Super Star

          再次声明,本字符串只为取代字符数组,至于其它的种种无理要求,均不在本座的考虑范围之内。

posted @ 2012-06-08 01:11 华夏之火 阅读(2670) | 评论 (13)编辑 收藏

玩具代码 24点游戏

     摘要:         所谓24点,就是甩出几个整数,整数之间没有固定的前后顺序,给它们添加上加减乘除括号等,形成一条式子,最后运算结果等于24。很自然的想法,就是祭出表达式树。但24点只是一个小程序,用表达式树来解决,实在有点大材小用了,太委屈表达式树了,所以坚决抵制。     &nb...  阅读全文

posted @ 2012-06-07 16:20 华夏之火 阅读(1835) | 评论 (1)编辑 收藏

有理数类的一点思考

        在写24点的程序时,要处理除法运算,不可避免地将引入小数点,但本人对于计算机中的浮点数实在没有太多的好感。权衡再三,终于决定下定决心写一个有理数类(以下简称为CRational),以解决除法运算这个问题。并且有理数这个类也是一个很好的C++练习题,可以用来练练手,以下将看到,它的实现虽然不难,但也不是很容易。有理数这个东西,属于用户自定义的数据类型,c++对此的支持,真可谓完美,既不失效率,又具备美观。c++可以让程序员做出来的自定义类型,其行为可以表现得好像是由语言层面实现的那个样子,不管从语法、安全、效率上讲。这一点,所有的语言都没法和c++媲美。
        不管怎么说,CRational的需求相当明确,它一定有分子、分母、然后支持加减乘除这四种运算,于是,一口气马上就能写下它的定义。
class CRational
{
public:
    CRational(
int nNumberator=0int nDenominator=1);
    
int Numberator()const return m_nNum;}
    
int Denominator()const return m_nDe;}

    CRational
& operator+=(const CRational& _Right);
    CRational
& operator-=(const CRational& _Right);
    CRational
& operator*=(const CRational& _Right);
    CRational
& operator/=(const CRational& _Right);
    CRational 
operator-()const    // 一元操作符,用以取反
    {
        
return CRational(-m_nNum, m_nDe);
    }


private:
    
int m_nNum;
    
int m_nDe;
}
;
         嗯,我承认代码很匈牙利,中MFC的毒太深。毋庸置疑,分子分母只能获取,不能设置,函数名中,没有加Get的前缀,实在是因为代码中它出现的地方太多了,所以能避免就尽量避免。没有涉及资源分配,析构函数可以忽略,拷贝构造函数和赋值函数也不用写了,编译器将会提供缺省的实现,足以满足我们的要求。貌似应该还要有一个返回求取小数值的操作,但是,这个类本身就是为了避免操作小数点,而且,计算小数点值可通过分子/分母的方法计算出来。总之,CRational的终极接口就是这个样子了。
        每个有理数都有一个标准的等价类,这个标准的有理数的分子、分母都不能再约分了,而且可以暂时假设符号位出现于分子中,分母则为正整数,比如说,3/6、-4/-8都等价于1/2,因此,在这个有理数类中,必须有一个标准化的操作,问题是,这个标准化函数是返回一个标准的有理数还是将有理数自身直接就标准化了。经过多方面的权衡,特别是为了兼容现有的整型变量(int, char, short等),整型变量可看成分母为1的有理数,我决定让CRational一直处于标准化的状态下,标准化的状态由CRational自己来维持,客户无须知道标准化的这个细节,因此,将void standarlize()声明于其private的区域下,其实现如下:
void CRational::standarlize()
{
    
if (m_nDe < 0)
    
{
        m_nDe 
= -m_nDe;
        m_nNum 
= -m_nNum;
    }

    
int nGcd = gcd(abs(m_nNum), m_nDe);
    m_nNum 
/= nGcd;
    m_nDe 
/= nGcd;
}

        其中,毫无疑问,gcd为最大公约数,gcd没有访问CRational的任何非静态(nonstatic)变量,因此,它必将不可成为CRational的非静态函数。将gcd声明为静态函数,虽然可避免污染全局空间,但也使得外部代码必须通过CRational来调用gcd函数,语法上不方便,而且,gcd本身就是一个独立性很强而且又通用的函数,从意义上,它也不应该属于CRational里面的东西。因此,gcd只能为全局函数。关于静态函数和全局函数的选择,鉴于“类的接口要尽可能的小”的原则和其他的一些问题,只要函数不访问类的静态变量,也不通过类的变量来访问到其非静态成员,它就应该是全局函数。全局函数是好东西,某些语言为了坚持所谓的纯粹的面向对象,故意不支持,实在让人用起来很不痛快,它的污染全局空间的问题,完全可以通过命名空间来解决。总之,只要可能的话,就应该将函数声明为全局函数,没什么不好。好了,请看gcd的实现。
unsigned int gcd(unsigned int x, unsigned int y)   
{   
    unsigned  
int  nTimes=0;   
    
for (; 0 == (x&1&& 0 == (y&1); x>>=1, y>>=1)
        
++nTimes;

    
if (x < y)
        swap(x, y);

    
while (y > 0)
    
{
        
for (; 0 == (x & 1 );x >>= 1 )
            ;   

        
if (x < y)
            swap(x, y);
        x 
-= y;
        
if (x < y)
            swap(x, y);
    }

    
return x << nTimes;
}
 
        其算法源于《编程之美》,可看成是非递归的版本。咦,怎么会这么长,与日常所见的到似乎不太一样,高效算法的代码貌似都会很长,好比strlen。再仔细看,里面居然没有取余的操作。嗯,它的算法核心,用移位和减法这两种快速的运算来替代取模这种慢速运算。
有了standarlize()之后,CRational的几个函数的实现如下所示:
CRational::CRational(int nNumberator, int nDenominator)
: m_nNum(nNumberator), m_nDe(nDenominator)
{
    assert(nDenominator 
!= 0);
    standarlize();
}


CRational
& CRational::operator+=(const CRational& _Right)
{
    m_nNum 
= m_nNum*_Right.m_nDe + _Right.m_nNum*m_nDe;
    m_nDe 
*= _Right.m_nDe;
    standarlize();
    
return *this;
}
……
        构造函数中,似乎应该检查分母为0的情况,然后抛出异常。但是,这属于契约使用的问题,用户违背的契约,一切后果,必须自己承担,我们的代码无须对此负责。
        此外,就是各种+、-、*、/、==、输出等各种全局运算符重载的操作了。得益于C++的缺省类型转换,我们不用再做其他事情,就可以很好地让我们的CRational很好地与融入到原有的各种整型世界中去。当然,为了效率起见,似乎有必要针对各种整型提供+、-、*、/的各种重载版本(complex就是这样做的),但在此,确实没有必要。缺省类型转换有时虽然会带来一些问题,但是,当确实需要它的时候,它就能发挥重大作用了。C++的各种特性就是这样,你可以不用,它也不打扰你(你要故意或无意用错,那也没办法),当真正需要到的时候,特性的威力就显示出来了。很多人之所以愿意沉迷于C++,就在于它不剥夺程序员的任何一点选择的权利。
ostream& operator << (ostream& outconst CRational& rat)
{
    cout 
<< rat.Numberator();
    
if (rat.Denominator() != 1)
        cout 
<<  "/" << rat.Denominator();
    
return out;
}


CRational 
operator+(const CRational& _Left, const CRational& _Right)
{
    CRational _Tmp(_Left);
    
return _Tmp += _Right;
}
……
        嗯,一再写这些入门级的文章,本座也很自觉有失身价,也算是对网络世界的一点回报吧。只要有一个人看了,能有所启发,在下就心满意足了,为免误人子弟,也欢迎有人批评指正。

posted @ 2012-06-04 11:23 华夏之火 阅读(1374) | 评论 (0)编辑 收藏

神奇的C数组

         数组在所有的语言中,以C最为简单,就是一起始地址,外加一数组的长度,而且基本上没有任何功能可言。然而,在所有的数组使用中,却是C的这种简单的数组形式,最一再使我心折,其灵活性和效率,简直惊天地泣鬼神,前无古人,后无来者,念天地之悠悠,高处不胜寒。
首先,C数组从逻辑上讲,是分形一致的(想不到其他词来形容了),分形的意思,就是部分与整体保持一致的构造形式,也即是数组的任何一部分也都是数组,比如一整型数组{1,2,3,4,5},连续取出其中任一连续的部分,都可以看成是一个数组,{2,3}是数组,{1,2,3}是数组,{4,5}也都是数组,差别只在于数组的起始地址和元素的个数。那怕是数组中的任何一个元素,都可以看成是一个长度为1的数组。因此,C数组的这种统一的格式,在作为参数,传递给函数的时候,特别是递归函数中,不知道有多么方便。
         比如冒泡排序,BubbleSort(int* pInt, int nLen),用递归来实现,只要冒完第1个元素之后,接着就可以递归,内部调用自己,BubbleSort(pInt+1, nLen-1),继续冒泡数组,只是数组是由第1个数之后的全部数组成的新的数组,元素个数比之前少1,一直如是处理,直到最后,数组的长度为1,于是冒泡排序完成。这种方法的成立前提,就在于C数组格式的高度统一。一个操作数组的函数,可以操作数组的任何一部分,甚至可以操作一个变量,因为单独的变量可以看成是元素长度为1的数组,多少次,我们用WideCharToMultiByte来转换一个宽字符变量。C语言中操作数组的函数,搭配上C数组的简单定义,其应用,那是相当相当广泛的,并且使用起来,自然非常非常的方便,比如刚才的冒泡函数BubbleSort,只要你高兴,完全可以拿来只冒数组中的随便某一部分,这完全可以由用户自己随意定制,语言层面上直接支持了。
         其他语言的数组,由于特点很多,反而失去了这种逻辑意义上的统一处理,因为在它们那里,数组的任何一部分都不能看成数组,单独的变量也没法看成是数组,在它们世界,数组与独立变量,无论如何,都没法划上等号。如果他们要用递归实现冒泡排序的代码,形式上无论如何都比不上我们的C数组形式的函数,谁说C的代码就意味着代码量多了,用得好,可以简单得让人赞叹不已。
         我们再来看看C的字符串,也很简单,它也是一个数组,只不过最后一个元素是’\nul’,加了这么一点限制之后,字符串自然就失去了数组的分形强悍,但C的字符串依然不可小看,因为字符串中,只要带上了’\nul’,都能看成是字符串,好比,”hello”这条字符串,只要改变起始地址,就可轻而易举地得到”ello”,”llo”,”lo”,”o”这好几条子字符串,这个特点,可以简化很多字符串操作,并且效率最高。此外,C字符串,只要你愿意,完成可以拿来当成是字符数组来使用,这样,就又恢复了数组分形功能,C函数库中和WINDOWS API,有很多函数就是专门处理C字符数组的。

        C数组的这种分形特性,在STL被抽象成迭代器,于是,在C++中,就失去了原有的辉煌。但是,在很多简单的设计中,依然有着不可低估的力量。

        C的很多东西,就是这样,因为简单,所以强大得令人心寒。函数、结构体、数组、GOTO、枚举这些简单的东西,巧妙的配合使用,可以玩出很多很多意想不到的神奇功能出来,令人击节赞叹,而且不会像C++那样,存在着所谓的什么心智负担。此外,C中的宏,嘿嘿,俺就不提了,变化多端,鬼神莫测。对于C,我越来越敬畏,它远远不似表面上看的那么简单,其背后自然潜藏着一套精神规则。即使看到每一行C代码,内心都能知道它的相应的汇编代码,那又怎么样,它总是有办法让你惊喜不已。

posted @ 2012-06-01 16:32 华夏之火 阅读(2184) | 评论 (12)编辑 收藏

类设计一则,GDI对象选入器

        虽然很痛恨MFC,但还是要经常使用MFC开发界面,虽然MFC怎么怎么的不好,但还是可以应付一般的界面要求,而且其运行效率也可以接受,关于这一点,它在WINDOWS3.1之时,就已经能胜任了,并且在下现在使用MFC,基本上也能随心所欲了,想怎么整就怎么整。为了减少以后浪费在MFC上的时间,我决定重构一套著名的MFC上的界面库。界面库这个东西,大家都知道,其各种美观的界面,基本上都是一笔一笔画出来的,代码中大量地使用了DC的各种操作,不可避免,就一再出现了好比以下类似的代码:

    HPEN pen = CreatePen(……);
    HPEN oldPen 
= (HPEN)SelectObject(hDC, oldPen);
    SelectObject(hDC, pen);
      ........
    DeleteObject(pen);

 

 

        这些代码,既无味,写起来又容易出错,它存在3个很明显的问题:1SelectObject使用了类型转换,一不小心,自然就转换错了,MFC中通过对象指针,内部做了转换,避免了这个问题;2、要将旧有的GDI对象选回DC中,很容易就遗忘了这一步代码,并且如果要同时操作几个DC的时候(这种情况较少见),在选回旧GDI对象时,那就更容易出错了;3、还要删除对象,这个,MFC中通过GDI对象的构造函数来消除这个问题。这样也罢了,但还有一个问题最让人不能忍受的,就是,代码中要定义一个旧的GDI对象变量,以最后选回设备环境之中,因为我最讨厌定义新变量,新函数了,每次都要变量名和函数名琢磨半天,当然,最讨厌的还是编写重复的代码了。于是,我决定编写一个类,以免总是要写这些毫无新意的重复代码,希望这个类是这样使用的:

CXGdiObjectSelector selector(hDC);
selector.CreatePen(……);
selector.SelectBrush(brush);
  ........

        不需要定义旧的GDI对象变量,画图完成之后,也不需要选回设备环境之中了,也不需要手工删除之前创建的GDI对象,一切,这个类都帮你代劳了,咦,这么神奇,它是如何做到的,自然是析构函数,再次向伟大的析构函数致以最高敬意。这个类,比之于刚开始的手工打造,它需要执行构造函数,以保存hDC到内部成员变量,很明显,多了一步赋值操作,此外,可以预料,这个类里面应该还有其他的初始化操作,这又是不可避免的多余代码。当然,这里的多余,都是我能接受的,我也深知,既要马儿,又要马儿不吃草,那是不可能的神话。但是,在实现这个类的时候,我想了种种办法,包括模板元编程也祭上了,始终还是存在其他多余的操作,再次审视刚刚开始的一段代码,不得不承认,它丑是丑了点,但执行的效率确实真他妈的高,而且所占的空间也很少,全部没有一丁点多余之处。好了,进入我们的类的实现。

class CXGdiObjectSelector
{
public:
    CXGdiObjectSelector(HDC hDC)
    
{
        ASSERT(hDC 
!= NULL);
        m_hDC 
= hDC;
        m_nSelectedFlags 
= 0;
        m_nCreatedFlags 
= 0;
    }


    
~CXGdiObjectSelector();

    
void SelectPen(HPEN pen)
    
{
        replaceObject(pen, XFLAG_PEN);
    }


    
void SelectBrush(HPEN brush);
      ........

    
bool CreatePen()
    
{
        HPEN hPen 
= CreatePen();
        
if (hPen == NULL)
            
return false;
        replaceObject(hPen, XFLAG_PEN);
        m_nCreatedFlags 
|= XFLAG_PEN;
        
return true;
    }

      ........

private:
    CXGdiObjectSelector(
const CXGdiObjectSelector&);
    
void operator = CXGdiObjectSelector(const CXGdiObjectSelector&);
    
enum {__nGDI_SIZE = 5};
    
enum {XFLAG_PEN=1, XFLAG_BRUSH=2, XFLAG_FONT=4, XFLAG_BITMAP=8, XFLAG_REGION=16};
    HDC  m_hDC;     
    HGDIOBJ m_hOldGdis[__nGDI_SIZE];
    WORD m_nSelectedFlags;
    WORD m_nCreatedFlags;
}
;

        整个类的定义的还是很直观。只是那一组创建GDI对象的成员函数,显得有点格格不入,根据单一职责原则,实在不应该加入这些东西,但是,加入这些操作,确实会给使用的时候带来极大的方便。至于禁用了拷贝和赋值函数,感觉有点多此一举,但是为了满足某些C++洁癖者的强迫症,我还是做了妥协。这个类其他代码的实现,相当简单,我就不赘述了。

        公道自在人心,这个类在选入选出GDI对象,毫无疑问,确实方便安全,但是它以牺牲执行效率和空间为代价。代码编写,不外乎是在做各种各样的权衡,有时付出类型安全,以换取更大的灵活性;有时又以付出灵活性,以换取类型的安全;有时以通用性换取效率;有时又要以效率换取通用。不能简单地说这种权衡好不好,只能说更加合适而已,在某一处上,比较强调什么,就以牺牲其他的特性来得到,谨记80%20%的原则。C++的深入人心,在于它不剥夺程序员选择的权利,同时它又提供了丰富的特性,以供你做各种各样的交换。通用、灵活、效率、安全,这四者总是不可协调的矛盾。MFC框架的最大错误就在于:牺牲了很大很大的灵活、效率、通用,最后只获得了一点点类型安全,这无疑是失败的交换。

posted @ 2012-06-01 10:56 华夏之火 阅读(1219) | 评论 (5)编辑 收藏

WINDOWS与设计模式

        作为C++的坚实粉丝,我一直很排斥JAVA,并不是JAVA这种语言不好,而是Java迷的那副嘴脸,事事都要与C++争,并且还坚称JAVA比C++,甚至连执行效率都要胜过C++,什么JIT运行时能监视代码,选取执行频率最高的代码,根据特定的平台,进行特别处理,生产出最优化的机器代码;又说什么垃圾回收能够解决C++中的内存管理问题,并且内存分配远远胜过C++中的手工人肉管理内存的毛病,其实,内存管理从来就不是C++中的严重问题,只要设计得好,应用层中的代码甚少可以不出现new, delete。至少delete可以不出现,大家知道WHY的;种种论调,无理取闹之极。而最令我受不了的,就是他们闭口开口,都离不开设计模式,搞得设计模式好像成为了JAVA的专有产品。须知,第一本设计模式的书,也是最最经典的那本四人书,其对设计模式的实现语言,采用的就是SmallTalk 和C++,一开始就不关JAVA的一点事情,并且书中C++还占了较大的比重。关于四人书,最让我欣慰的一件事情就是,四位巨头并没有响应JAVA迷的强烈呼声,采用JAVA来实现设计模式的第二版。当然,我也承认,用JAVA来实现设计模式,确实来得要比C++清爽,JAVA的这种语言,好像就是专为设计模式量身订做。只可惜,市面上任何一本JAVA的设计模式书,没有一本及得上我们C++的那一本设计模式圣经,C++中不必再需要设计模式的书了,因为最好的书就已经摆在那里了,涵盖了设计模式中的方方面面,浓缩精华得很。突然想起,C++的教材也不需要那么多,因为老爷子已经写了一本最好的书了,其他书的内容,都已经涵盖在那本C++语言圣经中了。至于那些不被C++圣经所提及的,那都是一些走火入魔的玩意,玩玩还可以,开阔开阔视野也不错,但真要用在实际项目中,还是少用为妙。罪过罪过,C++中的好书确实有好几本,不宜一棍子打死。
        有趣的是,设计模式在JAVA中被捧上了天,没有设计模式,JAVA就没法活下去。反而C++作为设计模式的第一实现语言,却不怎么感冒。不能说被轻视,但起码没有普遍被重视。即使C++中的高手,也没有对设计模式如何如何的推崇备至,他们貌似更加喜欢玩语法糖,热衷于在C++中模拟出其他语言的特性。这也难怪,C++有四种编程典范,设计模式的主要应用就在于面向对象,面向对象不过是其中最不成熟的一种,在C++中,最成熟的要数基于对象的编程,当然,拜C语言成熟,面向过程也熟得要烂了,泛型这东西也挺前卫的,而C++中的面向对象,一直臭名昭著,无论怎么努力,都难以搬上台面。注意,C++中的面向对象与虚函数是两码事,至少在我看来是这样的。
        好了,该谈谈我对设计模式的看法了,一句话,我对设计模式殊无好感,源于我内心对当前的面向对象编程风格的排斥,因为那是伪面向对象编程,所谓的伪面向对象,就是指通过单根继承和针对接口或抽象类来写代码,就以为是在进行面向对象的编程,那实在是太天真了。设计模式中的那些点子,用得好,确实能解决伪面向对象的一些问题,无可厚非;用得不好,无缘无故地引入一些不必要的东西,模式的应用意味着间接性的增厚。现实中,能恰当地用好模式的人,少之又少,模式总是出现在一些不必要出现的场合下。很多人都是生吞活剥了23种模式之下,内心就沾沾自喜,到处跃跃欲试,鲜有人尝试理解模式背后的统一思想是什么,或者说,如果代码本身就已经能够很好类与类之间的耦合问题,可胜过千万种设计模式。
        以下文字,与孟岩大侠的观点,存在部分重复之处,读者认为在下是在拾他的牙慧,我也不反对,毕竟人家的文章反表在前,我无话可说,本文的重点,旨在表达在下对设计模式的鄙视。
        既然有伪面向对象,就有真面向对象。真面向对象的编程,就是你只知道一个对象的ID,其他的一切事情,它继承自那些父类,实现了那些接口,统统一概都不得而知,然后你只能通过这个ID给对象发送消息,以此来驱使对象做一些事情,注意,确确实实是只是发送消息,而不是调用该对象的函数,那怕是调用了该对象实现的接口函数,也意味着该对象的依赖,好吧,说错了,是对该接口的依赖,不管怎么样,都是依赖,而且依赖接口,还搞得客户代码和对象都要依赖于同一个接口了。
给对象发送消息,听起来似乎有点抽象,但是,只要联想到WINDOWS的窗口句柄和消息处理函数,就自然明白是怎么回事了。在WINDOWS下,每一个窗口都是一个对象,窗口句柄代表了对象ID。操作窗口时,只能通过SendMessage或PostMessage来让窗口作一些事情。SendMessage或PostMessage的四个参数,分别是对象ID、消息编号、消息参数1和消息参数2。客户代码在使用窗口时,不需要假设什么接口,能做的,仅须做的,仅仅就是给它发送消息,客户代码完全无须依赖于什么鬼接口,非常简单明了。但是,这里存在一个问题,消息参数1和消息参数2都是void*类型,不安全耶,要是误发送错了消息,那该怎么办。在伪面向对象中,客户也可能在调用接口参数时,也可能会传错了参数,只不过是编译器可以帮你找出来。其实类型不安全,也没什么,客户既然要发送消息给对象,自然要遵守使用的协议,自然要清楚自己在做什么事情,这本来就是C语言的精神,一切相信程序员。
        好了,该是给WINDOWS大唱赞歌了。WINDOWS系统的窗口是我见过中算是比较象样的面向对象的典范了。将面向对象的精神贯彻到底,窗口对象很好地做到了仅仅是纯粹解析消息,执行消息,与外界的其他对象,不存在任何一点耦合。一个窗口对象中的lpfnWndProc其实可以理解成指向虚函数表的指针,但是它却要比虚函数表的指针功能更加强大灵活,并且还不存在虚拟函数表的种种问题。强大之处,就之于lpfnWndProc相当于指向一个庞大的虚函数表,这个表有2的32次方多个虚函数,灵活之处可以突破虚函数的种种限制。当你要给一个窗口对象添加新的功能,或者不满意它的某些原有操作的时候,你完全可以子类化该窗口,在子类化操作中,截取关心的消息,解析消息,执行消息,至于其他的消息,直接交给lpfnOldWndProc或DefWindowProc就是了。从这个意义上讲,所有的窗口对象继承于DefWindowProc,而子类化窗口即是继承于lpfnOldWndProc,但是,这里的继承,却没有伪面向对象中的继承或实现接口的种种弊端。而且,在你子类化窗口的时候,不用影响到客户的任何一点点代码,客户代码依旧是发送消息,却不知道窗口对象已经旧貌换新颜了,OH,这实在太美妙了。所有的耦合,奇迹般的消失得干干净净了。消息比之于接口,那完完全全是纯粹的正交关系,并且没有接口的种种累赘,种种缺陷。并且更爽的是,即使对象没有处理消息,也没有关系,客户代码依然可以给对象发送消息。
        从某种意义上讲,设计模式不过是为了解耦对象与对象之间的耦合关系,当对象之间不存在耦合的时候,设计模式还有什么存在的意义吗?以下结合WINDOWS系统来理解,所谓的设计模式,不过是消息发送的一些应用罢了。下面的举例,例子之间没有什么必然关系,我想到那里就写到什么,模式后面似乎应该带上英文,但我也懒得写了。

观察者模式:一个广播消息就搞定了;
模板方法:不过是按顺序给一个对象发送某些指定的消息而已;
工厂方法、抽象工厂:用一个或几个lpClassName就搞定了;
原型:不过是发送一条WM_COPYOBJECT的消息而已;
装饰者或者状态:嘿,子类化就完成了,并且非常彻底,一点都不觉得别扭;
命令:对象将SendMessage中的消息编号、消息参数1和消息参数2保存起来就是了,这没什么大不了的;
策略:不过一个回调函数,外加必要的参数;
桥接:貌似没什么必要;
……
没有了!落得个一片白茫茫大地真干净!

posted @ 2012-05-31 17:59 华夏之火 阅读(2190) | 评论 (8)编辑 收藏

试论C++类库开发之难

         很久很久以前,世界上曾经存在着这么一种语言,用它写出来的代码,既能保证高效的运行效率,又兼具优雅十足的美感,并且,所有语言存在着的不足,都可以通过添加新的自定义的类型来实现出来,并且,新的类型,只要做得好,可以与原有的类型无缝交互。在它的世界里面,那是一切皆有可能。并且,它还承诺,虽然它的功能非常丰富,但各个功能特性都可以和睦相处,而且,你代码中不必要用到某项功能时,你完全不必为这项功能付出那怕一丁点的代价,如果这个语言愿意作一点点妥协,可能它就不是现在的这个样子,至少将更加容易使用。所有它承诺的一切,乍听起来美妙无比,而且,它也似乎做到了,真的做到了马儿既能跑,又能吃尽量少的草,前提是开发人员要小心翼翼地用好这门语言的一切特性。只可惜事与愿违,这门语言确实看起来,真的可以创造出完美的东西,但事实上,没有人能用它写出过一个好东西,那怕是简简单单的字符串,要做得大多数人拍手叫好,也是千难万难,完美的字符串,从来就没有出现过。各位同学,自然知道在下说的是什么语言,等千呼万唤的C++1X始出来之后, 真不知道还有什么东西,是C++没法做的,要在C++中增加几种新的编程模式,也不会有太多的困难。起码,函数式的编程,使函数好像成为一等公民,绝不是什么难事。
          C++本身自然不会有什么问题,说它复杂吧,它确实复杂,但是完全可以无视,异常不好,禁用就是;多继承很坏,不用就是;模板过于复杂,忘了它吧;……,起码它做到了,你不喜欢的特性,你可以不必付出任何代价。但是,成千上万的C++高手大牛巨侠,这么多年下来,却没有搞出几个似模似样的类库出来,除了STL(其实STL也有一些问题),难道是程序员错了,难道是他们全错了吗?可是,C、JAVA、PYTHON它们,怎么就不存在这些问题呢,里头的好东西还真不少,起码在它们的圈子里面,码农们都觉得类库没问题,很好,用起来很放心,可以放心的使用。又或者是,C++码农对自家的类库要求过高了,他们的眼光太挑剔了,大家人人都有多疑症,但是,一旦他们使用其他的语言,又很乐意使用其他语言所提供的类库或者是框架,怎么就不会那样疑神疑鬼,没有各种并发症,起码在下是这样的,你要说C++没问题,那鬼才愿意相信呢?那么,假如C++有问题,问题又是那里呢?
          我以为,C++的问题,就在于它似乎很鼓励码农制造轮子,撰写类库,然后对所写的类库貌似没有过多的要求,但实际上,C++对类库的要求,是所有语言中最高的。好比,要你写的软件,需求很不明确,貌似用户说,你随便写,然后它能完成任务就可以了,但实际上这样的软件最难开发了。知道吗?C++的类库,有什么样的要求:1、运行的效率要高,处理一切细节,高效源于对细节的特定处理;2、表现出来的接口和语义要无限接近原始内嵌的类型,值语义,吖的,这要求也太高了。3、要有足够的通用性,满足抽象的需要。这三条的任何一条都不是什么省油的灯,直接可以K死秒杀多少C++高手的几千万只脆弱的脑细胞。这三条下来,本身就差不多是一个难以完成的事情,但是,这还不是最要命。最能搞死人的,是C++又提供了太多的丰富特性,什么模板、操作符重载、异常、多继承、内联、……,心理学的一项研究表明,当选择太多之时,当事人就会分散精力,不知道如何选择。用C++开发类库,眼前存在着无限种选择,貌似条条大路通罗马,但实际上,却是条条大路是死路。但这也没什么,最后一击,如果没有必要,鬼才愿意费心费神写类库,关键在于C++只是提供实现高标准类库的基本平台而已,语言本身就没有一点点能用的实在的东西,那怕一个简单的字符串类,一切都必须通过类库来完成,它一开始一直都在鼓励码农写类库,试问普天之下,有那个C++码迷,能够抑制造轮子的诱惑,于是,悲剧就诞生了。
          反观其他的语言,为什么就不存在这些问题呢?
          在C里面,是绝对不会出现C++的类库困境,首先,它只有一种选择让你开发类库,那就是函数和函数指针,外加结构体、数组,或者,必要的时候,使用上点宏,嗯,就只有这么多。你要用就用,不用就拉倒,在这么有限的条件,你只能做出最简单但却又是最精妙的设计,都能直达问题的本质。基本上,C的设计,不会存在一点点多余的抽象层次,一拳一拳,都打在要害上,针针见血。更何况,C的要求也不高,除了效率之外,就是要保持函数接口语义的清晰,而这并不难做到。
          那么,在高级语言中,又是另一番天地了。当然,它们也提供了多数C++的丰富特性,你自然有很多选择,但是,这些语义却屏蔽了很多底层的细节,并且提供了丰富的语言平台特性,什么反射啊、丰富的API框架,单是垃圾回收和单继承,就功德无量,足以拯救多少弱小脆弱的灵魂。是的,人家效率确实不太高,也没有要求值语义,但这不要紧,因为人家语言一开始就没有这样的效率标榜要求,它们一上来就让码农直面惨淡的应用,进入问题领域,不需要撰写什么高通用性的类,因为平台本身就提供了,你要写的类,都是问题领域中的东西。这实在能解救多少脑细胞啊,减少多少不必要的工作量啊呢。
          没有必要的类库来支持,C++做开发,工作量相当浩大,并且还容易生产出一堆不成熟的通用类;但是,必要的成熟的类库,市面上又没有多少个,目前的所谓的高级的C++类库,大多数都在为了C++而C++,你们知道我在说的是那一个的。当然,俺是没能力写出什么类库的,所谓的眼高手低,大概就是这样吧。
 或者,痛并快乐着,是C++码迷的持久不退的热情所在。只是,人生何其宝贵,将它们浪费在C++上,实在有点不值得。当然,活着只要快乐就好,C++也确实能给人带来很多快乐。

posted @ 2012-05-30 16:53 华夏之火 阅读(4374) | 评论 (31)编辑 收藏

MFC,一开始就错了

        谨以此文祭奠过去多少年下来,日日夜夜学习MFC源代码的宝贵时光。

        曾经以为MFC是世界上最神圣无比的东西,发誓在没彻底弄透MFC之前,绝不越雷池半步,碰都不要去碰LISP、ERLANG、PYTHON、VCL、ATL、STL、LOKI、BOOST各种美妙的好东西。即使一再听到高人说MFC设计得不好,非常糟糕,也都至死而不悔,然后只要稍微听到有人在说MFC的伟大,心里就特别高兴。买了各种各样的VC方面的书,什么四大天王、什么几十百千例、什么VC项目实例,彻夜苦读,单步调试,一次又一次地徘徊在MFC套来套去的各种美妙代码之中,观察里面各个对象的值。一次又一次地感叹MFC的伟大,一直在想,这辈子,如果能彻底地掌握MFC里面的一切秘密,朕就心满意足了。终于有一天,终于彻底醒悟,原来MFC真不是好东西,不过就是那些伎俩而已,真不该将它看得如此的伟大。这种念头一起来,再回过头来看MFC的一坨一坨代码,只觉得清晰无比,又感觉代码之中充满了各种无奈。接着再看看那些VC方面的书,除了四大天王还稍微有些可取之处,其他的任何一本,奇臭无比,没有一行代码经得起推敲, 文笔也糟糕无比,其内容又一再重复,抄来抄去,真正是一本又一本的狗屎。 感叹一下:这些书牺牲了多少宝贵的树木,又折磨死了多少读者的脑细胞,实在罪大恶极。

        在MFC大河日下的当今的大环境之下,在下也不想对它落井添石,毕竟也在其上学到不少好东西,特别是阅读代码的能力,试问这么糟糕复杂的框架都可以精通,天下还有什么代码能看不懂(别自信满满,你敢碰BOOST里面的那些奇技淫巧,匪夷所思的幻码吗)。但实在忍受不了躯体内另一个人日日夜夜,旦夕不停的诉求,兼之又一直恨铁不成钢,又为自己失去的青春阵痛不已。而VC2008中BCG的出现之后,VC2010还依然保留着那些蠢笨无比的BCG文件,不思悔改,终于让某彻底对MFC死心了,这货没得救了。

        好吧,废话少说,赶紧进入正题。

        国内C++界中一大侠说过,任何东西,都要先抓住其主干,其他的一切细节,都可以留待以后慢慢补充,否则,一开始就陷入细节,想跳出来就不容易了。大侠此语,实在有道理。(不知为什么,此大侠后期一直在对C++表示不满,当然,人家现在的境界,岂是我等小民所能体会)。不说其他,就说在C++中,必须站在一个高度上,纵观全局,才能将一切看得通通透透。于是,下面就尝试爬上高峰,来俯瞰MFC中的众生。MFC的主干,在下看来,既不是什么六大关键技术,也不是什么文档视图结构,更不是COM的封装,而是为了将原始的API中的各种对象,好比窗口、GDI、设备环境等,都映射成相应的多线程安全的C++对象,好比将HWND搞成CWnd,以期减轻WINDOWS下界面开发的工作量。

        这种想法,其实也无可厚非,如果真能封装得好,确实可以提高生产代码效率,但是从C++和WINDOWS的设计观念中来看,这种映射,还是存在很大的问题,稍后再述。问题在于,MFC的实现,异常丑陋呆板,通过几个全局的线程安全局部变量的映射表,将句柄(Handle,此词译得相当令人恶心)与其相应的C++对象指针形成一一对应,并且指定,在大部分的消息处理和参数传递中,所有的参数由原本的句柄一律由对象指针来代替,然后就高高兴兴地宣称已经完成任务了,这实在让人哭笑不得。不说别的,即使是消息处理,MFC即使已经做了很大的努力,也都无法将句柄参数搞成对应的对象指针,单是这一点,就已经失败了。然后再看看,它最后封装的做法,不过是将API函数中的第一个参数为句柄的都改成相应的C++对象的函数成员,好比将SetWindowText搞成CWnd::SetWindowText,美其名曰,薄薄的封装。这真是要笑掉大牙了,既然是这样的薄薄的封装,那还干脆不如不封装了,我想破了头,也看不出这样的封装,带来了什么样的好处。反而将原本全局函数的各种灵活性,都给葬送在类成员函数这个监狱中。全局函数的各种好处,除了是全局这个缺陷之外,实在都是类成员函数没法比的。而且,鉴于API中数量实在太多,于是搞出来的C++对象都是庞然大物,最臭名昭著的,当然要数CWnd和CDC这对黑风双煞了,使用起来,极不方便,极难学习。一个类的函数过多,无论如何,都是失败的设计。还有,如果WINDOWS系统新增了什么使用到句柄的函数,这些类也都要相应地增加新成员函数,再进一步说,如果用户自己因为需要,也要开发使用到句柄的全局函数,要怎么想办法整成员函数呢。其实,MFC中的所谓的线程安全,其实也不过是自欺欺人的说法而已,你要想在MFC中多线程的使用窗口对象,即使不是不可能,那也要花费九牛二力气才能在多线程中共享窗口对象,一切皆只因MFC已经很努力地做到了不让你在多线程下使用窗口对象。

        好了,再进一步考察,在这种思路的指导下,最后MFC被设计成一个什么样的框架。一直很怀疑想,MFC的设计者,是否不是对WINDOWS API的精神了解得不透彻,又或者对C++不是很精通,否则,无论如何,断断都不会整出MFC这样的一个人不象人、鬼不似鬼,精神分裂、做事虎头蛇尾的鬼东西出来。要不然,我们用MFC开发,就不会觉得束手束脚,很不好施展拳脚,一再感觉一直受制于其可恶的呆板的框架之下,这并不是说框架不好,只是MFC这套框架,实在很不好扩充。这样也罢了,问题是,MFC在使用上,又很不人性化,你又要陷入各种各样的细节之中,什么二段构造,有些对象又要自己删除,有些又不能自己删除等,更要命的是,MFC的运行效率又臭名昭著。有些同学就会问,貌似在MFC开发,其实也很强大很灵活,殊不知这种强大和灵活,那都是源于C++的强大灵活,也源于WINDOWS系统的强大灵活,源于MFC只对API只作了一层薄薄的封装,与MFC本身的设计没什么太多的关联。当抛开MFC,直接用API和C++来写代码,就体会到什么才叫做强大灵活,除了代码比MFC多一点之外,一切都要比MFC来得清爽,也要来得容易维护。如果,如果设计做得好,可能总体的代码量不会比用MFC的多也说不定。

        于是,这么一个伟大的框架,表现出来的,很多地方都C++的精神格格不入。承然,MFC刚开始设计时,缺乏太多的C++的特性的支持,而不得不另起炉灶。但是,就算没有C++98中的功能支持,MFC也不该出现以下这么重大的设计问题。
1、 不需要用到的特性,我们依然要付出种种代价。我们看到CCmdTarget集成了COM的功能,CWnd中对ActiveX的支持,就算我们的代码不包含一丁点使用到COM的代码,我们都要背负每个CWnd中将近100个字节的大小,运行过程中一再要忍受框架代码中对ActiveX变量的一次又一次地检查。我们看到,就算不使用文档视图结构,CMainFrame中都要承受对它的支持代码,和各个不必要的成员变量。总之,MFC中有很多特性,就算不愿意使用,它们还是在那里,不容许我们忽视。
2、 耦合太厉害。我们看到,CWnd中居然出现了对子类CView和CFrameWnd的引用,CControlBar的成员函数中有对CToolBar的CAST,然后,CFrameWnd又引用到CControlBar和CView,CControlBar和CView也都用到CFrameWnd的函数,各种各样好厉害的循环依赖,这么糟糕的设计竟然出现在C++的著名的界面框架,不得不令人叹息不已。这样的耦合,就是在MFC之中,一个类设计得再好,也只能在某些特定的场合下使用。好比, CControlBar, CSplitterWnd这两个好东西只能活在CFrameWnd,CView的环境下,想在其他的地方使用上就煞不容易了。就算是著名的文档视图结构,也都是问题很大,如果我的类,想要订阅文档中的通知消息,还要求必须派生于CView中,这个限制也太厉害了,于是为了它这个死板的设计,有多少次,我们必须改变设计,以适应MFC的无理取闹的要求。
3、 功能太过单薄:MFC仅仅是作为一个界面的基本框架而已,仅仅实现了从句柄到对象的映射,然后,再加上一点点所谓的文档视图的MVC模式,必要的多视图多子窗口的支持,基本的必要的COM支持,对API的薄薄封装,仅此而已(至于SOCKET、WININET和数据库的MFC类,又有多少可利用的价值)。有多少次,我们想对对话框进行拉伸的操作;有多少次,我们需要用到表格控件;……,但是,这一切,想要MFC提供更加强大的控件,完全都没有。必须求助于第三方控件公司库,好比,BCG、XTREME,你们知道,MFC的代码就算再烂,也还是很有分寸,还能保住最基本的底线,但是,第三方控件的代码,那真的是,让人大惊失色,没有做不到的,只有想不到的。要知道,C++可是很强大的,但是,用它做出来的框架,却如此的功能之小。
4、 MFC中的类的功能不单一,承载了太多的责任,导致很容易就出现了很多重复的代码:好比CCmdTarget中原本只是为了可以处理消息,后来居然还兼具COM的功能。又好比,CList, CMapPtrToPtr里面又都包含了内存池的实现代码,原本这些内存池的实现代码就应该剥离出来。
5、 行为错乱,神经分裂,没有统一的接口,以至于使用起来,极易出错。很多地方,都努力地去做了,但是都做得不彻底。企图封装所有的WINDWOWS的界面的API,但无论如何,都做不到,而且它自己内部也都知道没办法做到,但它还要努力地去做。
……

        各种各样的问题,难怪MFC的书那么多,没有一本可以作为面向对象的典范学习,即使四大天王,也不过只是没有那么恶臭而已。原来一切,皆只因为源头本就脏了。之前还以为,MFC,还能算是一积心堆砌而成垃圾。总体设计,虽然确实糟糕透顶,但细微上看,还能做到让人击节赞叹,好比对于线程局部变量的封装扩展,消息映射表。可是,即使局部代码的实现,也都充满了重重的缺陷。

        如果说MFC还有些可圈可点之处,VC2008之后引进的BCG的界面补丁,完全就将这些可稍为搬上台面的东西直接给淹没掉。BCG的出现,对MFC而言,根本就不是狗尾续貂,而是狗尾之上再绑上一只蠢笨无比的大猪。直接就将MFC推入万劫不复之地。

        MFC,OH SHIT,你可以去死了!

        突然惊觉,C++的类库框架,无不充满了各种各样的缺陷,除了STL还差强人意之外(其实也问题多多),没有一个能够让人看着满意,用着放心。于是,我要投入LISP和C的怀抱之中了,不,一定要顶住,C++是我的最爱。

posted @ 2012-05-30 14:15 华夏之火 阅读(19632) | 评论 (47)编辑 收藏

C++代码(4)排列与组合

     摘要:         书接上回,继全排列之后,接下来的任务自然是要给我们的Permutation添加上部分排列的功能。所谓的部分排列,为了便于行文的展开,我也不怕再举例,比如,针对0到4这5个数的P(3,5),其排列如下:012,013,014,021,023,024,……,234,240,241,…...  阅读全文

posted @ 2011-07-19 19:04 华夏之火 阅读(3043) | 评论 (5)编辑 收藏

仅列出标题
共5页: 1 2 3 4 5 

导航

统计

常用链接

留言簿(6)

随笔分类

随笔档案

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜