﻿<?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++博客-huaxiazhihuo-随笔分类-编程语言杂谈</title><link>http://www.cppblog.com/huaxiazhihuo/category/20583.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 10 May 2016 23:08:37 GMT</lastBuildDate><pubDate>Tue, 10 May 2016 23:08:37 GMT</pubDate><ttl>60</ttl><item><title>消息发送杂谈</title><link>http://www.cppblog.com/huaxiazhihuo/archive/2016/05/10/213491.html</link><dc:creator>华夏之火</dc:creator><author>华夏之火</author><pubDate>Tue, 10 May 2016 14:40:00 GMT</pubDate><guid>http://www.cppblog.com/huaxiazhihuo/archive/2016/05/10/213491.html</guid><wfw:comment>http://www.cppblog.com/huaxiazhihuo/comments/213491.html</wfw:comment><comments>http://www.cppblog.com/huaxiazhihuo/archive/2016/05/10/213491.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/huaxiazhihuo/comments/commentRss/213491.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/huaxiazhihuo/services/trackbacks/213491.html</trackback:ping><description><![CDATA[<div>&nbsp;&nbsp; &nbsp;&nbsp; 最近在看MFC的代码，虽然这破玩意，老朽已经很熟悉了得不能再熟悉了，但是这些破代码自由其独有的吸引力，不说别的，单单理解起来就非常容易，比之什么boost代码容易看多了，单步调试什么的，都非常方便，堆栈上一查看，层次调用一目了然。一次又一次地虐这些曾经虐过老朽的代码，也是人生快事一件。平心而论，mfc的代码还是写得挺不错的，中规中矩，再加上过去九十年代之初，16位的windows系统，那个时候的面向对象的c++的风靡一时，完全采用标准c++，能做成这样，实属难能可贵，也在情理之内。并且，后面压上com之后，mfc也不崩盘，采用内嵌类实现的com的方式，也很有意思。然后，从mfc中也能学到不少windows gui的使用方式还有各种其他杂七杂八东西，虽然win32已经没落。但是里面的技术还是挺吸引人，可以消遣也不错。当然，对于新人，mfc不建议再碰了，mfc真是没饭吃的意思。你想想，一个gui框架，没有template可用的情况下，而逆天c++11的lambda作为匿名functor，更加不必提了，只有虚函数和继承可用，也没有exception，能搞成mfc这副摸样，的而且确是精品。其实，后来的巨硬也有救赎，看看人家用template做出来的专为com打造的atl又是什么样子呢，然后建构在atl的windows thunk基础上开发的wtl又是怎样的小巧玲珑。巨硬在template上的使用还是很厉害的，atl将template和多继承用的真是漂亮。人家几十年前就将template和多继承用得如此出神入化，反观国内，一大批C with class又或者狗粉一再叫嚣template滚出c++，多继承太复杂了，运算符重载不透明，心智负担，隐式类型转换问题太多，virtual是罪恶之源万恶之首，构造函数析构函数背着马猿做了太多事情，exception对代码冲击太大，打断代码正常流行，时时刻刻都好像隐藏着不定时炸弹。依本座看来，C++中一大批能够显著减少重复代码，带来类型安全的拔高抽象层次的好东西，对于这些C语言卫道士而言，都是混乱之物。其实，c语言就是一块废柴，抽象层次太低，可以做文章的地方太少了。<br />&nbsp;&nbsp; &nbsp;&nbsp; 就以构造函数和类型转换operator为例，来看看怎么用于C的char *itoa(int value,&nbsp; char *str,&nbsp; int radix)。<br />&nbsp;&nbsp; &nbsp;&nbsp; itoa的参数之所以还需要str入参，那是因为C语言中缺乏返回数组的语言元素，所以调用者要提供一个字符数组作为缓冲用于存放结果，但是这个多出来str参数真是没必要啊，因为语言能力的欠缺，所以只好把这个负担压到猿猴身上。也有些itoa的实现没有这个累赘str的入参，而是内部static的字符数组，用于存放结果并返回给上层。这个版本就只有两个入参了，但是也太不安全了，别提多线程了。假如，有一个函数fn(char* str1, char* str2)，然后，这样调用fn(itoa(num1),itoa(num2))，画面太美了。另外，那个有多余str参数版本的itoa也好不到哪里去，要劳心费神准备两块字符数组，然后还要保证参数传递的时候不要一样。反正C语言的粉丝整天很喜欢写这些重复代码，并且美其名曰掌控一切细节的快感。<br />请看构造函数和类型转换operator怎么解决。太easy了。<br /><div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #000000; "><br /></span><span style="color: #0000FF; ">struct</span><span style="color: #000000; ">&nbsp;ToString<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">char</span><span style="color: #000000; ">&nbsp;text[</span><span style="color: #000000; ">28</span><span style="color: #000000; ">];<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">int</span><span style="color: #000000; ">&nbsp;length;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;ToString(</span><span style="color: #0000FF; ">int</span><span style="color: #000000; ">&nbsp;n)<br />&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000; ">//</span><span style="color: #008000; ">转换字符串，结果存放于text中</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">operator</span><span style="color: #000000; ">&nbsp;</span><span style="color: #0000FF; ">const</span><span style="color: #000000; ">&nbsp;</span><span style="color: #0000FF; ">char</span><span style="color: #000000; ">*</span><span style="color: #000000; ">()<br />&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">return</span><span style="color: #000000; ">&nbsp;text;<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />};</span></div><div>&nbsp;&nbsp; &nbsp;&nbsp; 并且，这里的ToString还可以安全的用之于printf里面呢，因为它本身就是字符串的化身。为什么是ToString，因为它不仅仅要它ToString int，还有double，bool，char，&#8230;&#8230;<br />&nbsp;&nbsp; &nbsp;&nbsp; 不好意思，扯远了，只是想说，框架或者函数库的表现能力也要取决于语言本身的表达能力。就好像C#就可以做出linq那样爽快的框架，java再怎么拼命也捣鼓不出来一个一半好用的linq，C++也不行，但是C++可以捣鼓类似于haskell中的map，filter，fold等，&nbsp; 并结合linq的后缀表达方式。就好比下面这样<br />&nbsp;&nbsp; &nbsp;&nbsp; vector&lt;int&gt; nums = {...}<br />&nbsp;&nbsp; &nbsp;&nbsp; Range(nums).Map(_1 * _1).Filter(_1 % 2).CopyTo(dest); // 用了boost中的lambda表达法，因为的确很简洁，没法控制。对于复杂情况，当然要用C++11原生的lambda<br />&nbsp;&nbsp; &nbsp;&nbsp; 勉勉强强差可满足吧。如果C++的lambda参数可以自动推导就好了，不过也没什么，主要是ide下用得爽。用泛型lambda也能将就。<br />&nbsp;&nbsp; &nbsp;&nbsp; 所以，回过头来，再看看mfc（没饭吃），就可以了解其各种隐痛了。真的，以90年代的眼光来看，mfc真是做到极致了。mfc不可能走win32下窗口函数C语言那样的消息发送消息反应的正路（邪路）吧。窗口函数这一套，在90年代面向对象盛行的时代，绝对不能被忍受，只是到了前几年，才被发现其价值原来不菲，这是解耦合砍继承树的好手法，老朽在前几年也跟风吹捧窗口函数的那一套。平心而论，smalltalk的这一套消息发送的动态语言，确实是很强有力的抽象手段，我不管目标对象能否反应该消息，闭着眼睛就可以给你发送消息，你能反应就反应，不能反应就拉倒，或者调用缺省的反应方式，就好像DefWindowProc（职责链模式？），又或者是抛出异常，怎么做都可以。一下子就解开了调用者和目标对象的类型耦合关系。面向对象中，消息发送和消息反应才是核心，什么封装继承多态，那是另一套抽象方式，虽然坊间说这也是面向对象的基本要素，但是不是，当然，这或许也只是个人观点。<br />&nbsp;&nbsp; &nbsp;&nbsp; 或许，从某种意义上讲，C++/java/C#一类的成员函数调用形式，其实也算消息发送吧。比如，str.length()，就是给对象str发送length的消息，然后str收到length消息，作出反应，执行操作，返回里面字符串的长度。靠，这明明就是直接的函数调用，搞什么消息发送的说辞来强辩，颠倒是非黑白，指鹿为马。可不是吗？编译器知道str是字符串类型，知道length成员函数，马上就生成了高效的函数调用方式。在这里，没有任何动态多态的可能，发生就发生了，一经调用，动作立马就行动，没有任何商量的余地。耦合，这里出现强耦合，调用者和str绑在一块了，假如以后出现更高效率更有弹性的string的代替品了，可是没法用在这里了，因为这里str.length()的绑定很紧很紧。<br />&nbsp;&nbsp; &nbsp;&nbsp; 人家消息发送就不一样了，动态的，可以动态替换对象，替换方法，弹性足足。并且，消息发送的模式下，对象收到消息，要判断消息，解析消息，找到消息的执行函数，最后才终于执行任务。这么多间接层，每一层都可以做很多很多文章。比如，在消息到达对象之前做文章，就可以搞消息队列，把消息和参数暂存起来，这个时候，什么actor模式就大放异彩，至于undo，redo，更加是小菜一碟。然后呢，再给对象安装消息解析器，把消息和消息参数转换成其他类型消息。比如原本对象不能反应这条消息，但是对消息参数稍加修饰，然后在发送给对象，这不就是适配器模式。总之，可操作可挖掘的空间太大了，远远不止23条。<br />&nbsp;&nbsp; &nbsp;&nbsp; 但是，封装继承多态就一无是处了吗？不是的，最起码一点，编译期间可以报错。因为的确有很多时候，我们明明就知道对象的类型，明明就知道对象不可能是其他类型，比如字符串，比如复数，比如数组这些玩意，无论如何，它们都不需要动态消息的能力。我们就知道手上的对象就是字符串就是复数，不可能是别的，并且，我们就是要明确地调用length函数。我们就是要编译器帮忙检查这里潜在的语法类型错误，比如对复数对象调用length函数，编译器马上就不高兴了。并且，一切都是确定的，所以编译器也能生成高效的代码，高效的不能再高效了。对此，消息发送的面向对象就做不到了，不管是什么对象，int，string，complex种种，都来个消息发送。这样一来，静态类型检查和高效的代码，就木有了。<br />考察一下，面向对象有等级之分，一步一步，有进化的阶梯。每进化一次，就多了一层间接，类型耦合就降低，就进一步超越编译器的限制，当然，也意味着编译器帮忙检查类型错误生成高效代码就弱了一分。事情往往就是，有所得必有所失。少即是多，多即是少。因此，可推得少即是少，多即是多。少始终是少，多始终是多。<br />&nbsp;&nbsp; &nbsp;&nbsp; 一切，还是要从C语言说起，C语言中，没有class，没有函数重载。函数名是什么，最后就是什么。在这种条件下，代码多了，每个新的函数名字要考究半天，一不小心，要么函数名字就会很长，要么函数名字短了要冲突或者不好理解。但是好处是，最后生成目标代码时，什么函数名字就是什么名字，所见即所得，没有异常，不会捣鬼，于是其他各种语言都可以高高兴兴开开心心调用。猿猴观码，也很清晰。C++也是在这里赚了第一桶金。其实，这么苛刻的条件下，最考究猿猴的代码架构能力，架构稍微不好，最后都势必提早崩掉，前期就可以过滤很多垃圾架构。<br />&nbsp;&nbsp; &nbsp;&nbsp; 然后就是C with class了，开始在函数名字上面做文章了。同一个函数名字依对象类型，开始拥有静态多态能力了。比如，str.length()，这是对字符串求长度。数组的变量，nums.length()，对数组求长度。同一个length的名字，用在不同的对象上，就有不同的意义。这如何做到呢，最初，cfront（第一版C++编译器）的处理方式是，可以说是语法糖，就是在名字和调用形式上做文章，比如，str.length()，变成，string_length(&amp;str)，array_length(&amp;nums)。别小看这点小把戏语法糖，这真是有力的抽象手法。不说别的，就说起名字吧，可以统一length了，无须费思量string_length，list_length了。然后，对象统一调用方式，str.length()，list.length()，函数确定这种吃力不讨好的事情就交给编译器去做好啦，解放部分脑细胞。这，的确很好，但是，全局函数是开放式的，而对象的成员函数是封闭的，一旦class定义完毕，成员函数的数量也就定死了。猿猴最讲究东西的可扩展性，不管成员函数多么方便多么抽象有力，就扩展性而言，就差了一大截，其他优势都弥补不了。语义上看，扩展成员函数的语法完全与原生的一样，增加一个简单的语法形式来扩充，但是多年下来，标准委员会都不务正业，哎。显然，编译器的类型检查能力和生成的代码性能，没有任何减少，但是，猿猴看代码，不能再所见所得了，必须根据对象类型，才能确定最终的目标函数。就这么点小改进，当时C++马上就展示其惊人的吸引力。假如，C++只能留在这一层，相信到今天为止，可以吸引到更多的c粉。可是，C++开始叛变。<br />&nbsp;&nbsp; &nbsp;&nbsp; C++的函数重载，还有操作符重载，外加隐式类型转换和隐式构造函数，还有const，volatile修饰，当然，代码可以写得更加简洁，编译器可以做的事情也更多啦，但是函数的调用再也不明确了。部分专注于底层的猿猴的弱小的抽象能力把控不住了，不少人在这里玩不动了。此外，命名修饰把最终函数名字搞得乱七八糟，二进制的通用性也要开始废了。导致C++的dll不能像C那样到处通吃。像是狗语言就禁止函数重载这个功能。大家好像很非难C++的操作符重载，但是haskell还能自定义新的操作符呢。虽然在这里，编译器还能生成高效代码，但是，各种奇奇怪怪类型转换的规则，编译器也偶尔表现出奇，甚至匪夷所思，虽然一切都在情理之内。<br />&nbsp;&nbsp; &nbsp;&nbsp; 其实，不考虑什么动态能力，单单是这里的静多态，基于对象（俗称ADT）的抽象模式，就可以应付70%以上的代码了。想想以前没有静多态的C日子是怎么过的。<br />&nbsp;&nbsp; &nbsp;&nbsp; 此时，开始兵分两路，C++一方面是动多态发展，表现为继承，多继承，虚继承，虚函数，纯虚函数，rtti（废物，半残品)，到此为止了，止步不前；另一方面是继续加强静多态，王者，template，一直在加强，模板偏特化，template template，varidiac tempalte，consexpr, auto，concept，&#8230;&#8230;，背负着各种指责在前进，就是在前进。C++企图以静态能力的强悍变态恐怖，不惜榨干静态上的一点点可为空间，累死编译器，罔顾边际效应的越来越少，企图弥补其动态上的种种不足。这也是可行的，毕竟haskell都可以做到。template的话题太庞大了，我们言归正传，面向对象。<br />&nbsp;&nbsp; &nbsp;&nbsp; 下面就是被指责得太多的C++多继承，虚函数，RTTI，脆弱的虚函数表，等，这些说法，也都很有道理，确是实情，兼之C++没有反射，没有垃圾回收，用上面这些破玩意捣鼓，硬着头皮做设计做框架，本来就先天能力严重不足，还要考虑内存管理这个大敌（循环引用可不是吹的），更有exception在旁虎视眈眈，随时给予致命一击。更要命的是，多继承，虚函数，虚继承，这些本来就杀敌八百自伤一千，严重扰乱class的内存布局，你知道vector里面随随便便插入元素，对于非pod的元素，不仅仅是移动内存，腾出新位置来给新对象安营扎寨，还要一次又一次地对被移动的对象执行析构拷贝构造。没有这些奇奇怪怪的内存布局，vector的实现应该会清爽很多。稍微想想，这实在太考究猿猴的设计能力，其难度不亚于没有任何多态特性的C语言了。可以这么说，继承树一旦出现虚继承这个怪胎，整体架构就有大问题，毫无疑问，iostream也不例外。不过，如果没有那么多的动态要求，好比gui框架的变态需求，严格以接口作为耦合对象，辅以function，也即是委托，又可以应付多起码15%的局面。其实，必须要用到virtual函数的时候，将virtual函数hi起来，那种感觉非常清爽，很多人谈virtual色变，大可不必。C#和java还加上垃圾回收和反射，这个比例可以放大很多。在这种层次下，接口最大的问题是，就好像成员函数，是封闭的。一个class定义完毕，其能支持的interface的数量也就定死了，不能再有任何修改。interface可以说是一个class的对外的开放功能，现实世界中，一种东西的对外功能并不是一开始就定死了的，其功能也在后来慢慢挖掘。但是，C++/java/C#的接口就不是这样，class定义完毕，就没有任何潜力可言了。明明看到某些class的能力可以实现某些接口，甚至函数签名都一样，对不起，谁让你当初不实现这个接口。对此，各种动态语言闪亮登场，或mixing或鸭子类型。接口还有另一尴尬之处，比如，鸟实现了会飞的接口，鸭子企鹅也继承了鸟，自然也就继承了会飞的接口，没办法不继承。面对着一个需要IFlyable参数的函数，我们顺利的传一只企鹅进去，然后企鹅再里面始终飞不起来，就算企鹅在被要求飞的时候，抛出异常，也不过自欺欺人。这种悲剧，就好像有些人很会装逼，最后一定会坏事。搞出接口这种破事，就是为了让编译器做类型检查的。又有人说，bird应当分为两类，会飞的和不会飞的，这的确能解决飞行的尴尬。但是，有很多鸟具备捉虫虫的能力，然后又有那么一小撮鸟不会捉虫只会捉鱼，难道又要依据捉虫能力再划分出鸟类。于是鸟类的继承树越长越高，画面越来越美。这分明就是语言能力的不足，把问题交给猿猴了。请谨记，接口就是一个强有力的契约，既然实现了一个接口，就说明有能力做好相关的事情。再说，既然interface这么重要，于是我们再设计class的时候，就自然而然把精力放在interface这个对外交流媒介的手段之上了，而忽视了class本身的推敲。class最重要的事情就是全心全意做好独立完整最小化的事情，其他什么对外交互不要理会。一个class如果能够完整的封装一个清晰的概念，后面不管怎么重构，都可以保留下来。但是，interface会分散这种设计。接口的悲剧就在于企图顶多以90分的能力去干一百分的事情，并且还以为自己可以做得好，硬上强干，罔顾自身的极限。往往做了90%的工作量，事情恰恰就坏在剩下来的10%上。<br />&nbsp;&nbsp; &nbsp;&nbsp; 于是，狗语言走上另一条邪路，鸭子类型。只要class，不，是struct，这种独特关键字的品味，只要某个struct能够完全实现某个interface的所有函数，就默认其实现了这个接口。并且，狗语言还禁止了继承，代之以&#8220;组合&#8221;这个高大上的名词了，但是，细究一下语义和内存布局（忽略虚函数表指针），你妈的，不就是一个没有virtual继承的弱多继承吗？显式的继承消失了，隐式的继承还存在的，好了，还不让你画出继承树关系图，高高兴兴对外宣称没有继承了，没有继承并不表示继承的问题木有存在。但是，因为狗语言的成员函数方法可以定义在class，不，struct外面，其扩展性就非常好了，对于一个interface，有哪些方法，本struct不存在，就地给它定义出来，然后，struct就轻松的实现了该接口，即使原来的struct不支持该接口，以后也有办法让它支持，很好很强大。之所以能做到这一点，那是因为狗语言的虚函数表是动态生成的。小心的使用接口各种名字，部分人应该狗语言用起来会相当愉快。可是，你妈，不同接口的函数名字不能一样啊，或者说，同一个函数的名字不能出现在不同接口中。不过，这个问题并不难，不就是不一样的名字吗，c语言中此等大风大浪猿猴谁没有见识过。对于狗语言，不想做太多评断，只是，其扩展性确实不错，非侵入式的成员函数和非侵入式的接口，理应能更好地应付接口实现这种多态方式，只是，编译器在上面所做的类型约束想必会不如后者，重构什么的，想必不会很方便。自由上去了，约束自然也下来了。听起来挺美，但是内里也有些地方要推敲，反正老朽不喜欢，以后也不大会用上，当然，给money自然会用，给money不搞c++都没问题。老朽还是比较喜欢虚函数的接口，更何况c++通过奇技淫巧也能非侵入式的给class添加接口。在静态语言中搞这种鸭子类型的动态语言接口，显得有点不伦不类。<br />&nbsp;&nbsp; &nbsp;&nbsp; 然后就是com接口的面向对象，完全舍去编译器对接口类型的约束，自然能换来更大的自由。由于com的语言通用性目标，所以搞得有点复杂，但是com背后的理念也挺纯洁。老朽猜测com好似是要在静态语言上搭建出一个类似于动态语言的运行平台，外加语言通用性。其契约很明确，操作对象前时，必须先查询到对象支持的接口，进而调用接口的函数。这里有意思的地方在于面对着一个com对象，你居然没有办法知道到它究竟实现了多少接口。<br />&nbsp;&nbsp; &nbsp;&nbsp; 最后就是消息发送了，其能力之强大，谁用谁知道。原则上讲，可以看成对象拥有的虚函数表的方法无穷多，又可以把每一条消息看成一个接口，那么，对象可能就实现了无穷多的接口。你说，面对着这样对象，还有什么做不出来呢。真用上消息发送这种隐藏无数间接层，就没有什么软件问题解决不了的。任何软件问题不就是通过引入间接层来解决的嘛。现在用上消息发送这种怪物，就问你怕不怕。没有免费午餐，自然要付出类型安全的危险和性能上的损失。<br /></div></div><img src ="http://www.cppblog.com/huaxiazhihuo/aggbug/213491.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/huaxiazhihuo/" target="_blank">华夏之火</a> 2016-05-10 22:40 <a href="http://www.cppblog.com/huaxiazhihuo/archive/2016/05/10/213491.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>scheme下的停机问题和Y组合子</title><link>http://www.cppblog.com/huaxiazhihuo/archive/2013/07/11/201689.html</link><dc:creator>华夏之火</dc:creator><author>华夏之火</author><pubDate>Thu, 11 Jul 2013 06:48:00 GMT</pubDate><guid>http://www.cppblog.com/huaxiazhihuo/archive/2013/07/11/201689.html</guid><wfw:comment>http://www.cppblog.com/huaxiazhihuo/comments/201689.html</wfw:comment><comments>http://www.cppblog.com/huaxiazhihuo/archive/2013/07/11/201689.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/huaxiazhihuo/comments/commentRss/201689.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/huaxiazhihuo/services/trackbacks/201689.html</trackback:ping><description><![CDATA[<div><div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 看过的计算机书中，scheme相关的那几本，好比SICP，the essence of program都很让我爱不释手。而the little schemer更加独特，编程的本质，在这本书小人书上体现得淋漓尽致。窃以为，scheme是语法形式上最为完美的编程语言了，没有之一。少即是多，这样的赞美之言，唯有scheme当之无愧，并且它的确是精简得不能再精简了。至于那个自吹自擂的什么狗语言，不提也罢。当然，完美并不一定代表实用，也并不一定必须流行，曲高一向都是和寡的，但是，完美却一定可以带来赏心悦目般的感受。</div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; the little schemer全书行云流水，逐渐显露递归的威力，做足了铺垫，到了第8章，真命天子lambda出现，一切变得很有意思了，读完之后，意犹未尽。第9章，难度陡增，突然变得理论性起来，那是自然的。因为，这一章的主题是停机问题和Y组合算子。不引入任何形式化的方法，但是作者举重若轻，依然阐释得如此直白易懂，可以说，只要能看完前面的内容，就一定能看懂这一章。而前八章，据说6岁以上的儿童都能看得明白。<br /><div><div><div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; scheme中的函数是first class，可以作为参数传递给其他函数，也可以作为值从函数中返回。比如，广为流传的一道程序，用以考察语言的表达能力，&#8220;编写一个函数，其入参数为n，返回值为新的函数，该函数的参数为x，返回值为之前的n与现在的x的和。&#8221;用scheme来表达，牛刀小试。</div><div><span style="color: #ff9900;">(define (addn n)</span><br /><span style="color: #ff9900;">&nbsp; (lambda (x)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp; (+ x n)))</span><br />然后，对于((addn 20) 30)，scheme解释器上显示其结果为50，很好。相比于那个lisp的版本，这里显得多么的干净。<br /><div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 函数式的语言对副作用（side effect）很敏感，特别是haskell，更加对副作用赶尽杀绝，压根就不让写出有副作用的函数。因此，正常情况下，函数执行完毕，都有返回值。好比，&#8230;&#8230;，总之很多就是，正常的函数，都可称之为total functions，意思就是对所有的参数，都会有返回结果。但是，也还存在一些病态函数，它们不会返回，一旦调用它，那么将陷入与其中，永远都不会返回了，显然，里面出现死循环了，但是，scheme中没有循环语句，所以不能这么说，总之，这一类是不会有返回值的。很轻易就能写出一个例子。<br /><div><span style="color: #ff9900;">(define eternity</span><br /><span style="color: #ff9900;">&nbsp; (lambda (x)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp; (eternity x)))</span><br /><div>eternity为不朽的意思。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 自然就有这样的问题，能否实现这样的函数，它能判断函数是否终将返回，或者说，判断函数会不会停止。这个函数作用可大了，当然不会那么容易实现。不过，可以先假设它存在，就叫它will-stop?（别惊讶，scheme中，标识符中可以有+-*等特殊符号）。因此，对于任何函数，(will-stop? afunction)表达式的值，要么为#t，表示函数afunction终将停止返回；要么为#f，则函数不会停止，好比eternity。显然，让will-stop?判断自己，(will-stop? will-stop?)的结果一定是#t了。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 但是，will-stop?是不可能存在的，这不是废话吗，地球人都知道。因为计算机学家精心构造了一个反例，此反例实在巧妙，真难以想象当初是如何构造出来，我等小民只需理解即可。请看代码<br /><div><span style="color: #ff9900;">(define (last-try x)</span><br /><span style="color: #ff9900;">&nbsp; (and (will-stop? last-try) (eternity x)))</span><br />last-try，好名字，就叫它最后一击吧。(will-stop? last-try)的结果不外乎#t或#f。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 假如为#f，说明last-try不会返回，意味着有死循环，不会停止。但是，一考察last-try的内部实现，却很容易就知道它马上就返回了。表达式(and (will-stop? last-try) (eternity '()))中，由假设可知(will-stop? last-try)为#f，进而马上可知，(and (will-stop? last-try) (eternity '()))马上必将返回#f，也就是说，虽然一开始假设last-try不会停止，但实际运行中last-try一下子就返回了，矛盾。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 看样子，(will-stop? last-try)只好为#t了。可是，(and (will-stop? last-try) (eternity '()))，and表达式的两个分支中，既然(will-stop? last-try)为#t，那么，势必要进一步调用(eternity '())，而eternity老爷，一早就知道他乃不朽之身了，因此，last-try也沾光，一样不朽了。与假设中(will-stop? last-try)为#t为终将停止，又是矛盾。</div><div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 因此，will-stop?接受不了last-try的挑战，失败。也就是说，will-stop?这样的函数，不存在。这道反例的高明之处，或者说耍赖吧，就是以will-stop?为基础构造了一个will-stop?无法判断的函数。假如规定，所有被检测函数都不得直接间接的调用will-stop?，免得will-stop?难堪，那么这样的will-stop?能否存在呢？存不存在，我就不知道了，但享受此待遇的Y组合子却是存在的。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 函数直接或间接调用到它自己，递归就产生了。问题来了，函数你自己都还没实现完毕，怎么就可以自己拿来调用呢？这个过程中，编译器解释器肯定做了某些语义上处理，让递归得以实现。逻辑学中，对于下定义的要求是&#8220;不得循环&#8221;，好比，白色就是一种白色的颜色，这种废话定义就不符合下定义的基本要求了。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 下面来将一条经典的递归函数整成非递归的版本。the little schemer的推导思路非常浅显易懂，我不能做的更好的了，因此借用。</div><div><span style="color: #ff9900;">(define length</span><br /><span style="color: #ff9900;">&nbsp; (lambda (l)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp; (cond ((null? l) 0)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp; &nbsp;&nbsp; (else (+ 1 (length (cdr l)))))))</span><br />函数length中，虽然调用到了自己，实际上，其实只是调用了一个同样名字的函数而已。意味着，length的实际上的lambda表达式，背地里带多了一个参数，此参数为函数，用以当入参l不为空时来进行使用。因此，可以将整个函数的定义改写成下面的lambda表达式。<br /><span style="color: #ff9900;">(lambda (length)</span><br /><span style="color: #ff9900;">&nbsp; (lambda (l)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp; (cond ((null? l) 0)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp; &nbsp;&nbsp; (else (+ 1 (length (cdr l)))))))</span><br />lambda表达式的返回值为一个函数，当然没有名字了。它的入参为一函数，返回一个新的函数，此新函数的入参是列表，返回列表的长度。为了便于后文叙述引用，就用define给它起个名字，叫mk-length。什么，连用define起名字都不会，没救了。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mk-length不是需要函数入参吗？刚好手头有一个，就用它自己本身，((mk-length mk-length) '())，解释器返回0，太好了。然后，我满怀希望的用((mk-length mk-length) '(a))来测试，结果，解释器报错了，为什么？稍微一想，就明白了。(mk-length mk-length)的确返回计算列表长度的函数，但是，当列表不为空时，只好用表达式(+ 1 (length (cdr l)))做进一步处理，里面的length就是mk-length，而mk-length的入参是函数，不是列表，于是解释器就报错了。怎么办？<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 当然，要计算长度为不大于1的列表的长度，还是有办法的。就是，((mk-length (mk-length mk-length)) '(a))，这样就好了。自然，当列表大于1时，解释器必然又将报错了。按照此法，为此，为了求得不大于N个元素的列表长度，必须将mk-length写N次，好比，<br /><span style="color: #ff9900;">((mk-length</span><br /><span style="color: #ff9900;">&nbsp; (mk-length</span><br /><span style="color: #ff9900;">&nbsp;&nbsp; (mk-length (...))))</span><br /><span style="color: #ff9900;">&nbsp;'(a b c d ...))</span><br />并且，辛辛苦苦的重复写N遍mk-length，只能计算个数不大于N的列表的长度。这，无论如何都不能让程序猿接受。<br />那么，为何要写那么多(mk-length (mk-length (mk-length...)))，皆因mk-length中(+ 1 (length (cdr l)))的length函数接收的函数参数是列表l。先暂时让它适应环境，就让它知道它接收的length参数是一个跟它自己本身的lambda表达一样，是入参为函数，然后返回一个计算list长度的函数。将mk-length改写成这样。<br /><span style="color: #ff9900;">(define mk-length</span><br /><span style="color: #ff9900;">&nbsp; (lambda (length)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp; (lambda (l)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (cond ((null? l) 0)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; (else (+ 1 ((length length) (cdr l))))))))</span><br />请注意，代码里面已经不存在递归形式了，因为，mk-length的lambda表达式中，没有用到mk-length这个名字了，当然，它还要用到入参length以计算当l不为空时的长度。再次抱着试试看的态度，验证，((mk-length mk-length) '(a))，返回1，真的可以了。拿更长的列表丢进去，长度为2，为3，为N+1，都OK了，真是神奇。</div><div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 它的工作原理是，故事一开始，(mk-length mk-length)生成一个计算列表长度的函数，在其内部中，假如列表l为空，就返回长度为0；否则，就计算l的尾部长度，并加上头结点的长度1，而计算l的尾部的函数，是通过(length length)来生成，其中length就是mk-length，故事就回到原点(mk-length mk-length)了，只是，其返回值在外围中要加1了，然后，在更外围中继续加1，加1，&#8230;&#8230;。<br />但是，工作还没有完成，因为，mk-length中，((length length) (cdr l))很刺眼，它应该是(length (cdr l))这样的形式。重构，必须重构。必须在将其提炼成一个函数，因此，mk-length就变成<br /><span style="color: #ff9900;">(define mk-length</span><br /><span style="color: #ff9900;">&nbsp; (lambda (length-mk)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp; ((lambda (length)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp; &nbsp;(lambda (l)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp; &nbsp;&nbsp; (cond ((null? l) 0)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;(else (+ 1 (length (cdr l)))))))</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp;&nbsp; (lambda (x)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ((length-mk length-mk) x)))))</span><br />代码似乎变得复杂些了，但效果是一样，并且，语法结构上基本保持一致。但是代码好像的确变得更长了，这也没办发，为了保持最内部length的纯洁性。但是，它也太深了，作为重点，应该放在外面，嗯，应该将两个lambda对调一下。<br /><span style="color: #ff9900;">(define mk-length</span><br /><span style="color: #ff9900;">&nbsp; (lambda (length-mk)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp; ((lambda (length)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (length (lambda (x)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ((length-mk length-mk) x))))</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp;&nbsp; (lambda (length)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (lambda (l)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp; &nbsp; (cond ((null? l) 0)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (else (+ 1 (length (cdr l))))))))))</span><br />面对着这么多的lambda，实在难以淡定。但必须接收洗礼，方可体会到函数作为一等公民，所带来的强悍的表达能力，简直能撞破习惯命令式编程的眼球。里面的lambda(length)又变回原来的样子，但是，mk-length的主体已经不再是它了，而是一个以的lambda(length)为参数的lambda了。为了保持mk-length的纯洁，继续努力，这一次，是在两个(mk-length mk-length)上做文章，每次都要写两个相同的函数，不如把它做成函数。事情到了这一步，Y组合子已呼之欲出。<br /><span style="color: #ff9900;">(define Y</span><br /><span style="color: #ff9900;">&nbsp; (lambda (f)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp; (f f)))</span><br /><span style="color: #ff9900;">((Y mk-length) '(a b c d e))&nbsp;&nbsp; &nbsp;;返回5</span><br />然后将mk-length中的第一条length的lambda搬过来，并且作为两个f的入参<br /><span style="color: #ff9900;">(define Y</span><br /><span style="color: #ff9900;">&nbsp; (lambda (length)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp; ((lambda (f)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (f f))</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp;&nbsp; (lambda (length-mk)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (length (lambda (x)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ((length-mk length-mk) x)))))))</span><br />最后，将Y整得更加好看一点，也看来更加的通用，不仅仅是针对length，而是全部的需要递归的函数。<br /><span style="color: #ff9900;">(define (Y f)</span><br /><span style="color: #ff9900;">&nbsp; ((lambda (g) (g g))</span><br /><span style="color: #ff9900;">&nbsp;&nbsp; (lambda (g)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp;&nbsp; (f</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (lambda (x) ((g g) x))))))</span><br />再送上一道求和<br /><span style="color: #ff9900;">((Y</span><br /><span style="color: #ff9900;">&nbsp; (lambda (sum)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp; (lambda (n)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (cond ((= n 1) 1)</span><br /><span style="color: #ff9900;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; (else (+ n (sum (- n 1))))))))</span><br /><span style="color: #ff9900;">&nbsp;10)</span><br />文章已经很长了，打住。以后再发挥吧。</div></div></div></div></div></div></div></div><img src ="http://www.cppblog.com/huaxiazhihuo/aggbug/201689.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/huaxiazhihuo/" target="_blank">华夏之火</a> 2013-07-11 14:48 <a href="http://www.cppblog.com/huaxiazhihuo/archive/2013/07/11/201689.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C语言复杂声明的本质与局限</title><link>http://www.cppblog.com/huaxiazhihuo/archive/2013/07/01/201425.html</link><dc:creator>华夏之火</dc:creator><author>华夏之火</author><pubDate>Mon, 01 Jul 2013 07:57:00 GMT</pubDate><guid>http://www.cppblog.com/huaxiazhihuo/archive/2013/07/01/201425.html</guid><wfw:comment>http://www.cppblog.com/huaxiazhihuo/comments/201425.html</wfw:comment><comments>http://www.cppblog.com/huaxiazhihuo/archive/2013/07/01/201425.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/huaxiazhihuo/comments/commentRss/201425.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/huaxiazhihuo/services/trackbacks/201425.html</trackback:ping><description><![CDATA[<div>&nbsp;&nbsp;&nbsp; 先简单回顾一下C语言的独有的变量声明方式。自诩使用C语言多年，却一直对于C的复杂的变量声明方式头皮发麻，直到看到VCZH大神前不久的大作，才恍然大悟。惭愧，因此下面的内容颇有拾人牙慧之嫌，但为了引出后面一系列关于语言的随笔，也没办法了，本文的荣誉都归于vczh大神。就从最简单的说起。<br />&nbsp;&nbsp; &nbsp;int a;&nbsp;&nbsp; &nbsp;// 说明表达式a的值是int型，a自己本身也是int型，这不是废话吗？<br />&nbsp;&nbsp; &nbsp;int array[N];&nbsp;&nbsp; &nbsp;// 于是，表达式array[n]的值为int型，array是int数组，是否废话的味道少了一点？<br />&nbsp;&nbsp; &nbsp;int *pA;&nbsp;&nbsp; &nbsp;// 显然，*pA的值为int型，而pA的类型是指向int的指针。<br />&nbsp;&nbsp; &nbsp;int fun(int x, int y)&nbsp;&nbsp; &nbsp;// 毫无疑问，表达式fun(a,b)的值为int型，fun则是函数，其函数签名是&#8230;&#8230;<br />&nbsp;&nbsp; &nbsp;通过前面例子，说明一个道理，可以从另外一个角度来理解C变量的类型声明，先确定整个表达式的结果值的类型，再考察变量本身的类型。就好比以上几个例子，a（单独一个变量都是表达式）, array[n], *pA, fun(a,b)这些表达式都是int型，定义变量的语句的类型，其实就是为了说明这个语句的变量的整个表达式的结果的值的类型。<br />&nbsp;&nbsp; &nbsp;好了，请深呼吸，开始重口味了，下面的注释，其实都是废话。<br />&nbsp;&nbsp; &nbsp;int *fuck[N];&nbsp;&nbsp; &nbsp;// *func[n]的类型为int，因此，func[n]的结果类型为int*，因此，func的类型为数组，数组的元素为int的指针<br />&nbsp;&nbsp; &nbsp;int (*pfuck)(int x, int y)&nbsp;&nbsp; &nbsp;// (*pfuck)(a, b)的结果类型为int，看到(*pfuck)，括号内出现一元操作符*，此物为求得指针的所指之内容，然后，此内容还能进行函数调用，因此可知，pfuck为指针，指向一函数，该函数的签名是&#8230;&#8230;。当然，表达式pfuck(a, b)也可以得到相同的结果，但是，为了强调pfuck的类型，请坚持使用(*pfuck)。<br />&nbsp;&nbsp; &nbsp;int* (*pfuck)(int x, int y)&nbsp;&nbsp; &nbsp;// *(*pfuck)(a, b)的值为int，pfuck的类型自然是函数指针，函数签名是有两个int型的参数，其返回值是int*<br />&nbsp;&nbsp; &nbsp;int (*func[5])(int *p);&nbsp;&nbsp; &nbsp;// 毋庸置疑，(*func[i])(int *p)的结果是int型。它表示先获取数组的一个元素，对元素解引用，进而函数调用。显然，func为长度5的数组，数组元素是函数指针，函数有一int*行的变量，返回值是int型。<br />&nbsp;&nbsp; &nbsp;int *(*func())();&nbsp;&nbsp; &nbsp;// 心里发麻是不是，要淡定。不管怎么样，*(*func())()的结果始终都是int值，是不是？从最外围上看，*(...)()，此乃一函数调用，然后对返回值解引用得到的值为int。我们知道，C语言中，只有两物可进行函数调用的操作，或函数，或函数指针，两者必居其一。有以上例子分析可知，*(*func)()此乃对函数指针的函数调用结果求指针值。现在，又有*(*func())();，括号内的*func()，分明就表示func的函数调用，此函数的返回值为指针。结合最外层的函数调用，此返回值指针指向一函数，也就是说，返回值是函数指针。因此表达式*(*func())()，涉及到两个函数调用，它表示内层的函数调用返回函数指针，而此函数指针再调用一次，其结果为int*，再用上指针*运算符，整个表达式的值就为int了。因此，func是一函数，此函数返回函数指针，函数指针指向一个无参而返回值为int*的函数。曲折离奇，大功告成。<br /><br />&nbsp;&nbsp; &nbsp;好了，该反过来想了，如何从变量的类型来构造其定义语句。好比，&#8220;fuck指向一个数组，其个数为5，数组元素为函数指针，函数签名为带一个(int *p)参数，返回结果是int&#8221;。<br />&nbsp;&nbsp; &nbsp;先考虑如何使用此变量。既然fuck是数组指针，那么，*fuck就是返回其所指向的数组，然后要得到数组的元素，自然理所当然必须用到[]操作符了，因此，就得到，(*fuck)[i]了，注意，千万切记，必须加括号，否则，*fuck[i]意味着fuck自己本身就是数组了。自己本身是数组，和指向数组，也即，数组和数组指针的差别，是相当大的，其差别之大就好像整型类型和整形指针类型。然后，必须不能忘记的是，一元操作符*就是取得指针的所指之物。<br />&nbsp;&nbsp; &nbsp;好了，总之，对于fuck，我们通过(*fuck)[i]得到数组元素。既然元素又是函数指针，进而就得到，(*(*fuck)[i])(pa)，这个表达式的值为int。因此，答案就是，&#8220;int (*(*fuck)[5])(int *p);&#8221;。<br />&nbsp;&nbsp; &nbsp;代码写成这样子，真他妈的贱，尽玩文字游戏，写的人费心，读的人糊涂。这该死的C语言，shit！<br />&nbsp;&nbsp; &nbsp;文章突然长了，打住。不惜对完整的类型进行分离，以求得声明与使用的语法的高度一致性。C语言真是，真是精致得让人大倒胃口。<br /><br />&nbsp;&nbsp; &nbsp;又：有时候，对于稍微复杂一点声明的常用类型，会经常出现重复的声明语法，特别是在函数指针的时候，为了拟补这种缺陷，或者说是痛苦，或者说是对于变量类型的重视，C语言提供了typedef的关键字。用以代表这种声明与使用的一致性的变量的类型。在前面的例子中看到，声明语句中的类型，只是说明变量采用这种表达式时，它的就是这种类型。好比，int *pArray[20]，*pArray[i]的值为int型，但pArray却绝不是int型，为了取得pArray的类型，可借助typedef；具体的使用如下，typedef int* IntArray[20];，然后，IntArray pArray;以定义同样类型的变量。又好比上例，int *(*func())();这个函数声明好像让某些人难以理解，用上typedef化简一下，就可以重点很突出了：<br />&nbsp;&nbsp; &nbsp;typedef int* (*FunFuck)();&nbsp;&nbsp; &nbsp;// FunFuck代表无参返回值是int*的函数指针类型；<br />&nbsp;&nbsp; &nbsp;FunFuck func();&nbsp;&nbsp; &nbsp;// 作用相当于int *(*func())()，但含义更加鲜明。<br />&nbsp;&nbsp; &nbsp;可以看到，typedef的用法很简单，不过是在过去的表达式的前面加一个typedef而已。后话，typedef在C++的template中，扮演了非常非常重要的角色，特别是模板元编程MPL中，全部的类型演算全部压在它身上，其作用之大，简直是惊天地泣鬼神，没有了typedef，C++的template不过是普通简单的泛型编程，有了template对typedef的完善支持，其实就是在struct/class内部中支持typedef语句，就导致了tmp的横空出现，导致C++的template成为威力最恐惧，同时语法也是最恐惧的泛型语言，没有之一。<br /><br />&nbsp;&nbsp; &nbsp;继续补充：加上const。以上对于复杂声明的理解，明眼人一看就知道仅仅是从右值的角度入手。要理解const，就必须不可不提到左值了。左值右值是C++中的基本概念，三言两语也说不清楚。最粗浅的看法，左值指可以被写入，也就是说能出现于赋值语句的左边；右值指可读，只能在赋值表达式的右边。当然，一般来说，左值往往可以当做右值来使用，可写往往意味着可读。而const的作用，就是将原本可写的东西，给整成只读的了。具体到表达式来说，就是某些表达式的值具备左右值，而const就是去掉了它的左值功能。举例说吧，还是从最简单说起。<br />&nbsp;&nbsp; &nbsp;int a; 表达式a的结果是int型，既是左值又是右值； <br />&nbsp;&nbsp; &nbsp;const int a;，a返回的结果是int类型，但是此结果已不再可写了，也即是a不能放在赋值语句的左边了。<br />&nbsp;&nbsp; &nbsp;int const a; 可这样理解，先不理int const，a是一个变量，既可读又可写，const将a整成只读。int表示const a的结果类型。虽然，理解上这样，但对编译器来说，const int a;和int const a;都一样，都只是表达了同样的意思，a是一个整型常量。<br />&nbsp;&nbsp; &nbsp;const int *p;，*p结果为int型，加上const后，*p只能读了，所以，p是整形指针，其所指的内容只能读不能写，但p本身却可写。 int const *p;，则先强调*p的只读性，然后再说明*p为int型。其实，这两种写法的意思都一样。<br />&nbsp;&nbsp; &nbsp;int *const p;，const 紧挨着p，说明p本身只读。至于 int *，则表示*p类型为int，可读写。因此，p是整形指针，p本身不可写，但其所指的内容却可读又可写。说实在，不明白这样的指针变量有什么鬼用，实际的代码应该用的很少才是。<br />&nbsp;&nbsp; &nbsp;为了表达指针的只读纯洁性的概念，不仅指针本身不能写，连其指向的内容也不可修改，C++终于整出了下面这样伟大的代码。int const *const p;或者const int * const p;。C++的这种做法，俗称致力于解决臆想中的问题，因为const与指针的组合，实际上只有指针所指向的内容只能读很有意义，其他的，意义微乎其微。<br />&nbsp;&nbsp; &nbsp;可见，原本C的声明使用一致性的语法，遇上const之后，开始有点混乱了。当半路中杀出C++的引用之后，这种语法的一致性在C++中就不复存在了。C++的很多语言特性，都在不同程度不同角度，深度和广度上，形式和语义上，给C语法的精致性造成致命的各种各样的冲击。以至于，最后C++变成了有史以来很难很复杂超级变态恐怖的语言了，没有之一。</div><img src ="http://www.cppblog.com/huaxiazhihuo/aggbug/201425.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/huaxiazhihuo/" target="_blank">华夏之火</a> 2013-07-01 15:57 <a href="http://www.cppblog.com/huaxiazhihuo/archive/2013/07/01/201425.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>