分类
哲学

2019-12-18-哲学家们都干了些什么-笔记

哲学家们都干了些什么

林欣浩

前言

“中华民族是世界上最智慧的民族”这句话,我们没听过一千遍,也有一百遍了。但是在生活中,我们会遇到这样的情况:当小孩子难以抑制内心的迷茫和惶恐,急匆匆问爸爸妈妈“人为什么活着”的时候,他得到的回答常常是:“别胡思乱想。”当大学生在宿舍里如饥似渴地阅读康德、黑格尔,想从中寻觅一丝真理的时候,他或许会被打球回来的同学们迎面嘲笑:“又装×呢?”当酒桌上的朋友们都在谈汽车房子情人孩子今年我去了巴厘岛的时候,某人却兴致勃勃地大谈他最近读叔本华的心得,他换来的,多半是满桌审视异类的目光。咦,我们不是号称“世界上最智慧的民族”吗?为什么到处都回荡着对思考者的排斥和嘲笑呢?当我们不断追问“人为什么活着?人生的意义是什么?宇宙的本质是什么?”的时候,绝大多数人不会觉得我们是爱思考的聪明人,只会觉得“你这个人好怪”。

学哲学有什么好处呢?有时我们会问:“人为什么活着?人生的意义是什么?人终有一死,我该做点什么才对得起这唯一的一生,才算没有白活过?”这些问题很重要,是决定人的一生该怎么活的大问题。我可不可以不听从长辈和课本的灌输,自己来寻找问题的答案呢?可以,哲学就是来干这件事的。我们这本书,就要把“追问人生意义”当作最大的目标。

第三章 使徒行传

犹太教和基督教并不是同一个宗教。首先是在犹太人中产生了犹太教,而基督教是从犹太教中发展来的。犹太教和基督教都信奉上帝,也都相信会有救世主来拯救他们(“基督”和“弥赛亚”是一个词,都是“救世主”的意思)。区别是,基督教认为救世主就是耶稣,而犹太教不承认耶稣是救世主,他们认为救世主还没有到来。在对待经文上,两者都信奉《旧约》[插图],但只有基督教相信《新约》。《旧约》和《新约》的区别大致在于,一个是记录耶稣降生之前的事,一个是记录之后的事。

这是不是说明了他们信奉的上帝不靠谱。

历史上有一个规律,在斗争中,哲学总站在弱者的一方。这是因为哲学讲思辨,讲道理,而只有弱者才会去讲理。强者不需要讲理。这也是因为,哲学继承了苏格拉底讨人厌的疑问精神。只有弱者在面对强权的时候,才有质疑权威的需要。

不要说这么直白嘛

《圣经》说上帝是全知、全能和全善的,那为什么会允许人间存在这么多丑恶和痛苦?我们知道,《圣经》里说亚当和夏娃偷吃了禁果,违反了上帝的禁令,所以被逐出伊甸园,所以人类才会开始无尽地受苦。但上帝是全知的,不仅知道过去所有已经发生的事情,还知道未来所有即将发生的事情。那么前面那个问题就可以问成:上帝既然知道亚当和夏娃会偷吃禁果,为什么一开始不去阻止他们?奥古斯丁的解释是,关键在于自由。上帝给了亚当夏娃和人类自由意志,所以也必须让人类有作恶的可能。更具体地说,上帝是善的,而上帝的善表现在上帝对人类的行为要进行公正的赏罚。既然要赏罚,前提是人类必须拥有自由意志,必须有能力自己选择行善还是作恶,否则人类就不应该对自己的行为负责。这段论证对我们的意义是:首先,它十分巧妙,把一个看似自相矛盾的说法给解释开了;其次,这解释强调了自由的重要性。上帝允许人类有作恶的自由。这说明什么?这说明在上帝看来,自由比善更重要。可是等一等,上帝不是全善的吗?“上帝允许人类拥有自由”的理论是奥古斯丁出于护教目的提出的,其推论却和教义产生了矛盾。矛盾还不止如此,该理论还可以推论出,上帝不能干涉人的自由意志。因为上帝是万能的,所以有能力预测出人们按照自由意志在未来会作出的各种恶,但是有很多恶上帝都没有阻止。可是,上帝不是全能的吗?因此,奥古斯丁的解释虽然聪明,却不是很受基督教的欢迎。

奥古斯丁其实没有完全解决这个漏洞,哲学家早就证明了,上帝不可能是全能全知全善的。

宗教天生拒斥思考。

所以一定要警惕宗教。

宗教和哲学本来就不能调和。保留哲学,对教会来说就是养虎为患。现代著作家威尔・杜兰就把亚里士多德的哲学比作希腊人留给基督教的“特洛伊木马”。总有一天,苏格拉底的讨厌精神也会让教父们抓狂的。

作为一个业余哲学家,看了这段话,我对这些哲学家更有好感了。

第四章 上帝之城

有句俗话叫“能用钱解决的问题都不是问题”,其实还可以说一句话:“必须用暴力解决的问题都是解决不了的问题”。当强者对弱者使用暴力的时候,正说明强者没有别的招数可用了,也就说明他离失

还有一句话是暴力不能解决问题,但是它能摧毁问题。

第五章 异教徒

九次十字军东征只有第一次算是攻其不备,取得了胜利,后面的八次全部失败。还有一次最奇特,十字军根本没有去打阿拉伯人,而是把东罗马帝国给抢了。要知道,十字军东征名义上的原因,是东罗马帝国扛不住阿拉伯人了,向西边的基督教兄弟求援。谁想基督教兄弟比异教徒还凶狠,东罗马帝国这个惨啊。

哈哈哈哈哈哈!

第六章 神们自己

阿奎纳提出了五个方法来证明上帝的存在。这五个方法形式相近,我们只举其中一个最简单的,大致概括为:世上万事万物都要有另一个事物作为它的原因。那么必然存在一个最初的原因,这个原因就是上帝。这个思路很精彩,它能够完全靠逻辑推理,而不是靠神学教义来证明上帝的存在。我们之后要介绍的很多哲学学派,也是用类似的形式来证明,上帝是存在的。但是假如您和我一样,并不是虔诚的基督徒,那么这个证明还不能满足我们的需要。这是因为,从追问人生意义、追求个人幸福的角度说,上帝对于我们普通人最重要的意义在于:他是全知、全能、全善的。而且人类的灵魂必须永存不灭。换句话说:第一,上帝必须能够知道我们一生中所有的行为和遭遇。第二,上帝必须有无上的善良,以便能对我们的行为进行公正的评判。最好这评判标准还能事先公开,比如通过《圣经》的教诲。第三,上帝也必须有无上的能力,可以对每个人实行奖惩。第四,必须保证每个人的灵魂不灭,这样未来的奖惩才有意义,不至于让我们陷入虚无的深渊。只有具备了以上条件后,上帝的存在才能为我们提供巨大的安慰,才能够指引我们的行动。然而阿奎纳的证明只是说世上存在一个我们无法感知的巨大力量,却无法证明那股力量就是上帝,以及上帝能够具备以上几点能力。

感觉并不高明啊。

阿奎纳的证明本身也有问题。罗素反驳说:那什么是上帝存在的原因呢?如果“万事必有因”,那么上帝的存在还要有自己的原因,上帝如果要依赖于外物存在,那么上帝就不是全能的。假如说上帝不依赖于外物存在,那么“万事必有因”就不成立,那我们就允许有事物不依赖原因存在,那你为什么说这初始因就一定是上帝呢,也可以是其他事物啊。对于上帝的理性证明,罗素还有一个反驳。罗素说,上帝是全知、全能、全善的。那么,上帝要不要符合善恶标准呢?假如上帝要遵守的话,那么上帝就有了自己必须遵守的规则,就不是万能的了。假如上帝可以不遵守善恶的标准,那么上帝就无所谓善恶,也就不是至善的了。当然,神学家可以辩护说:上帝是人类不能理解的。上帝的善和人类概念里的善是完全不同的。作为经院哲学的集大成者,阿奎纳对上帝的证明无法令人满意,刚好说明了用哲学推导神灵这条路终究走不通。实际上,用哲学去证明宗教,本身就有一个致命的漏洞。经院哲学家想得挺好,他们用哲学去证明宗教,为的是让宗教也能符合理性的考验。但是别忘了,怀疑是哲学的核心精神。当神学家们试图证明上帝存在的时候,这不也就意味着上帝有可能不存在吗?按照基督教的教义,基督徒绝不能质疑上帝的存在。那么可以说,当神学家们把哲学引入到神学的一瞬间起,他们就已经开始背离自己的信仰了。

果然被哲学家搞得一头包。

第七章 群魔

中国人对待宗教有更多实用主义的倾向,信宗教大多是为了要点好处。而且佛教说的是因果报应,就算你不信佛,多做好事也可以有好报。不像基督教讲人有原罪,光做好事没用,你不信仰基督不受洗就进不了天堂。中世纪的教会认为,刚出生的婴儿如果没来得及受洗就夭折了,那也是要下地狱的。

第九章 奇怪的论调

哲学和科学一样,也有现成的产品呀!那就是充斥在我们生活中的各种各样的人生观。当邻居大妈默念“人的命天注定”的时候,她信奉的是宿命论和决定论;当朋友在酒桌上劝你“赚钱有什么用,钱再多早晚也是一个死”的时候,他讲的是虚无主义;当人生感悟型的散文告诫你“当下最重要,活出你自己”的时候,它其实就是萨特的代言人。实际上,整个哲学史上那么多学派那么多说法,其中凡是和普通人有关系的观点,我们都可以在生活中找到它们的通俗版、谏言版、人生感悟版、心有戚戚焉版。我们不需要了解真正的哲学理论,就已经在“享用”哲学家们的思考成果了,并没有什么精妙的哲理是独独藏在哲学著作里,是我们在日常生活中享受不到的。你想,假如这世上存在一种让人易于接受的,又能给人带来好处的道理,人们没有理由不把这个道理改写得通俗易懂,然后拼命到处传播呀。每个人天生都趋乐避苦。那么假如哲学书中真有什么对人类有好处的人生道理而大众却不知道的话,这不就意味着只有我们才是全世界最精的人,而这世上其他所有不读哲学的人都是比我们笨的傻子吗?这不大可能吧。

我们享受科学成就最好的办法是买个新手机而不是去学《电子电路》一样,如果我们的目的是为了找一个对自己有好处的人生观,那我们没必要学习哲学,只需要从各种世俗的人生观中选一个就好了。假如明白了这一点,你还是不满意各种世俗的人生观,执意要翻开哲学书亲自研究一番的话,那么就只有一个原因了:你不信那些现成的答案。你怀疑它们。祝贺你,你被苏格拉底附体了。为什么苏格拉底宁愿死,也要怀疑?为什么我们放着现成的快乐不享受,非要亲自学哲学?因为我们是人,不是动物。人和动物的区别在于人要思考。而怀疑是思考的起点,也是思考成果的检验者。怀疑的最大作用在于能避免独断论,这样才能引导我们寻找正确的答案,免得我们轻信一切未经证实的结论。

第十章 童年的终结

度高者重表,测深者累矩,孤离者三望,离而又旁求者四望。触类而长之,则虽幽遐诡伏,靡所不入。

这种形式的勾股定理,想想就觉得恐怖。

第十一章 理性主义

我们的结论必须能经得起各种怀疑,这样才能保证它真实可信。这也是科学研究的原则。

欧氏几何是什么东西呢?它一共有五条公设和五个公理。这些都是欧几里得硬性规定的。然后他的整个几何世界,所有的定理,都是从这几条公设和公理中演绎推理出来的。我觉得,咱们普通人只要一学欧氏几何,肯定都匍匐在地上把它当成神了。您先看看它的五个公理和前四个公设,不用细看,扫一眼就行:公理一:等于同量的量彼此相等。公理二:等量加等量,其和相等。公理三:等量减等量,其差相等。公理四:彼此能重合的物体是全等的。公理五:整体大于部分。公设一:任意一点到另外任意一点可以画直线。公设二:一条有限线段可以继续延长。公设三:以任意点为心及任意的距离可以画圆。公设四:凡直角都彼此相等。感觉到了吗,这些公理和公设都超级简单,全都是小学课堂上一句话就可以带过的知识。大部分在我们看来就跟废话一样,都想不出,写出这些来能有什么用。然而,就是这么区区几句话,竟然能一路推理推理,写出厚厚的前六卷《几何原本》来,内容能够涵盖世间所有的平面几何知识。几何世界千变万化,大自然中的几何图形更是无穷无尽,都逃不过上面这简单的几句话。这能不让人膜拜吗?但这还不是最牛的。咱们来看看剩下的第五公设。内容是:若两条直线都与第三条直线相交,并且在同一边的内角之和小于两个直角,则这两条直线在这一边必定相交。你一看不对劲了吧。这个公设超级复杂,跟前面的公理和公设的简洁形式毫不搭配。更可疑的是,在《几何原本》里,第五公设仅仅在第二十九个命题里用过一次。就好像是一个根本没必要的累赘一样。

第十二章 形而上学

“形而上学”这个词英文是metaphysics,它的来历是这样的:话说回到古希腊。亚里士多德是个百科全书式的学者,他写过很多的著作,从哲学到物理学,涉及了很多学科。但是那个时候没有现代学术界“哲学”“物理学”这样的分科。亚里士多德是写痛快了,想研究什么就写什么,可给整理他书籍的后人犯愁了。这么一大堆包罗万象的著作,该怎么分类、命名呢?一个叫安德罗尼柯的人想了一个好办法。他用“研究有形体的事物”和“研究没有形体的事物”,把亚里士多德的著作分成了两大类。前一类著作编在一起,起名叫《物理学》。后一类作品,也就是亚里士多德的哲学作品,也编在一起,放在《物理学》的后面。当时没有合适的名字称呼它们,安德罗尼柯一看怎么办呢,就给起了一个名叫metaphysics[插图],意思是“物理学之后”。安德罗尼柯起这个metaphysics的原本目的,应该是他没有现成的词汇可用,于是就说这部分著作是“编排在《物理学》之后的内容”。但这个词的含义也可以引申成“物理学之后的学问”。也就是说,形而上学研究的是那些高于物理学的、看不见、摸不着的学问。这就是“形而上学”这个词最早的来历。

“形而上学”的中文译名也很棒,称得上是中文翻译史上最棒的译名之一。中文典出《易经》:“形而上者谓之道,形而下者谓之器。”《易经》的这句话很精彩,也很好理解。“形”,就是有外形、可以触摸、可以感知的东西。《易经》的这句话,是下了两个定义。第一个定义是说,超过我们感知之外的那些无形的东西,是“道”。“道”,就是“道理”的“道”,指的是“道理”“概念”这些抽象的东西。老子说“大道无形”,就是这个意思。第二个定义是说,我们能感知到的那些有形的东西,是“器”。“器”是“器具”,就是指“东西”“物质”。让人拍案叫绝的是,《易经》的这句话,和安德罗尼柯的思路是一模一样的。《易经》的“道”,对应的就是安德罗尼柯的metaphysics。《易经》的“器”,对应的就是安德罗尼柯的“物理学”。于是日本哲学家井上哲次郎先生在看到metaphysics这个词后,联想到《易经》,把metaphysics翻译成了“形而上学”。简直是“信、达、雅”的最高境界。

很多小孩喜欢不停地问家长“为什么”,让家长不胜其烦。其实,这个“为什么”的游戏玩到最后,追问的往往就是形而上学的问题。举个例子。小孩问爸爸:“我为什么要上幼儿园啊?”爸爸回答:“因为爸爸妈妈要上班,不能照顾你呀。”“那爸爸妈妈为什么要上班啊?”“因为爸爸妈妈要挣钱啊。”“那爸爸妈妈为什么要挣钱啊?”“挣钱了才能买吃的啊。”“那为什么要买吃的啊?”“有了吃的,人才能活啊。”“人为什么要活着啊?”一般问到这种地步,家长就准备打人了,对吧?可是,家长要打人并不是因为孩子无理取闹——求知怎么能算是无理取闹呢?而是因为家长没有能力回答这个问题,他们恼羞成怒了。因为孩子最后问的“人为什么活着”的问题,正是形而上学最重要的问题之一。加缪说过:“真正严肃的哲学问题只有一个,那就是自杀。”研究“人为什么不自杀”,其实就是在研究“人为什么活着”。你看这孩子一下子就提出了一个最根本的哲学问题,一般的家长怎么可能回答得上来呢?

我儿子有几次就把我问得一头汗,因为我答不上来,他的问题更简单,为什么呢?其实前面一个问题他未必理解了,但是他还会接着问后面一个问题为什么呢。

第十四章 唯我论

笛卡尔说过:“不管多么荒谬、多么不可置信的事,无一不是这个或那个哲学家主张过的。”这句话使他不仅成为了伟大的哲学家,还成为了哲学史上伟大的预言家。在笛卡尔之后,我们将会看到更多稀奇古怪的奇思妙想。您会发现,您小时候觉得自己有过的特离奇的想法,这帮哲学家们早就全都给想遍了。

突然对这本书充满了期待。

第十七章 名利场

亚里士多德是柏拉图的学生,但是观点和柏拉图相悖,为此亚里士多德还说了一句名言:“吾爱吾师,吾更爱真理。”你可以把这句话理解成亚里士多德对真理的浓浓爱意。但你也可以理解成:“有理就说理,别拿辈份压我!”

好吧。

有个皇族成员想光耀门楣,叫莱布尼茨替他写一部家族历史。莱布尼茨满口答应了(可能还是他主动提出来的)。您想,其实这不就是个软文吗?你找点史据把他们家夸一顿不就完了嘛,随便来个会写字的都能干这活儿。然而莱布尼茨是怎么写的呢?他说,这个家族的历史,是整个皇族历史的一部分,必须和整个皇族的历史结合在一起写。但是要研究皇族的历史呢,又必须先研究地理。然而这片土地又是地球的一部分,所以我们要从地球的形成开始研究。然后那个皇族成员左等右等也不见莱布尼茨的书写完,就派人去看看。结果那人一看就崩溃了:莱布尼茨正兴致勃勃地写远古时代的地球发展史呢。所以说,古板的知识分子就算是想市侩,结局恐怕也是悲哀。

我笑喷了。

第二十一章 暴风雨

什么叫因果律呢?你不能说因果律就是“一件事的发生是另一件事发生的原因”,这相当于同义反复,说了跟没说一样。因果律是什么呢?在经验世界里,我们可以把因果律说成:“如果A事件发生了,那么B事件一定会发生。”更严格的说法是:一、A事件发生在前,B事件发生在后。二、二者发生的关系是必然的。比如说,苹果必然落地的事件我们可以分解为:一、“苹果离开树枝”发生在前,“苹果落地”发生在后。二、这个关系是必然的。想象一下如果我们是一个一无所知的小孩子,我们只靠经验,怎么能知道苹果一定会落地呢?唯一的办法就是,我们一遍又一遍观察到“苹果离开树枝”和“苹果落地”这两件事总紧接在一起发生。我们就明白了,哦,苹果这东西原来不可能飞上天去啊。但问题是,通过经验,我们观察到的只是因果律中的第一条:A事件发生在前,B事件发生在后。那么第二条呢?这个关系的必然性我们是怎么观察到的呢?这个“必然”能让人看见?这个“必然”能让人感觉到?没有,“必然”这个东西不在我们的经验范围之内。我们之所以认为这里有“必然”性,是因为我们过去无数次地看见了这两件事连在一起发生,所以我们就想当然地认为,这两件事之间有必然的联系,在未来也会永远连在一起发生。休谟尖锐地指出:这种想法是错的。

什么叫做彪悍,这就是了。

第二十三章 谎言的衰落

这是在笛卡尔出生半个世纪之前,那时候还没有马丁・路德,还没有新教,连赎罪券都还没有呢,就已经有人看罗马不爽了。这个人是神圣罗马帝国的皇帝亨利四世。他觉得自己很牛,为啥我的国家非要每年给罗马教会捐那么多钱呢?他就开始和罗马吵,吵到后来他竟然宣布罗马教皇是伪僧侣,要其下台。教皇对付这种不服的主儿,只有一个办法:“绝罚”你。虽然教皇翻来覆去只有这么一招,但这招儿太灵了。我们前面说过,欧洲国王管不住自己手下的领主,领主们又信奉教会。亨利四世被“绝罚”后,立刻叛乱四起。亨利实在扛不住了,无奈之下,不得不千里跋涉来到教皇的住所前求饶。贫民出身的教皇拒绝接见他,亨利就在大雪中站了三天三夜(一说还没穿鞋子),然后教皇才出来让他吻了自己的鞋,宽恕了他。这个例子经常被提起,用来证明中世纪教皇的权威。但是有些文章忘了说故事的后半段:亨利四世是个很厚黑的家伙。回去以后一看没什么事了,嘿嘿一笑从怀里掏出一个小本儿来:谁当初背叛过我,我都记着哪。没过多久,亨利四世把当初背叛他的人都给灭了。稳定了局势后,他立刻翻脸再次讨伐教皇。教皇只有一招呀,“绝罚”呗。连圣斗士都知道同样的招数不能用两遍,何况是对政治家。面对第二次“绝罚”,亨利四世啥事也没有,直接带兵杀到了罗马。教皇只能从罗马仓皇出逃,最后凄凄惨惨地客死异乡。当然,这时候教会势力还很强盛,后来继任的教皇又把局势扳回去了。宗教改革的时候,新教还拿这件事出来说,用来激励日耳曼人的民族情绪,号召人们为亨利四世报仇。

好吧,我一直都只知道这个故事的前半段,没想到还有后半段。一个神反转。前半段说的是教会多牛逼,后半段就变成了教会傻逼了。

第二十四章 远离尘嚣

康德是个奇幻作家,给我们设计了一个架空世界。这世界是什么样的呢?在这个世界里,人类是一种非常可怜的生物。人类永远无法认识到这个世界的真面目。人类所感受到的这个世界,都是通过人类心灵中某个特殊的机制加工处理过的。这个负责加工的机制,我们起个名字叫作“先天认识形式”。世界的真面目,起个名字叫“物自体”(也被译作“自在之物”)。人类感觉到的世界,也就是“物自体”经过“先天认识形式”加工后得到的东西,我们把他(们)叫作“表象”。这几个名词,得麻烦大伙记一下了。也就是说,我们生活中看到的桌子啊,椅子啊,这些都是世界的表象。桌子和椅子的真面目是物自体,到底是什么样子的我们永远无法知道。要特别说明的是,这个先天认识形式,也就是人类心灵对物自体的处理机制,每一个人都是一样的。这个“先天认识形式”一词中的所谓的“先天”,不是说这东西是生物学上的天生的本能,不是像理性主义者说的那样是一种超越了客观世界的存在,它既不是人类生理的表现,也不是心理的表现,不会因为人体的变化而改变。所以你说我想改改自己的“先天认识形式”,这是不可能的。顺便我们再学一个小词汇:“先验”。“先验”和“先天”差不多。意思是,先于经验,说某些东西是在人获得经验之前就存在的。这些东西不依赖于人的经验而存在,而且常常会决定着人的经验。显然,先天认识形式就是先验的。再比如理性主义者相信的不言自明的公设,一般人理解的绝对真理,也都是先验的。

康德认为,这世界(物自体)是人类永远无法真正认识的,人类看到的只是表象的世界。但是由于每个人对真实世界的表象方式(先天认识形式)都是相同的,所以人类看到的同一个东西的感受还是一样的,因此我们察觉不到真实的事物是否被扭曲了。所以这个世界观并不和我们的生活经验相悖。那因果律是怎么回事呢?康德认为,我们这个先天认识形式里,包含了很多用来处理物自体的工具(一共有十二个先天范畴),其中一个就是因果律。而科学家们只能研究我们感觉到的事物。也就是说,科学家只能研究表象世界,因此科学家的研究对象都是带有因果律的。那么,人的自由意志又在哪儿呢?我们自己的意识就是物自体啊!因果律只存在于先天认识形式里,并不存在于物自体中。物自体是自由的,我们自己的意识也是自由的。换句话说,康德让人的意志受到了先天认识形式的严密保护,因果律不能穿透先天认识形式去控制人的内心意志,所以人仍旧是自由的。当然,这也意味着作为物自体的自我意识,是没法被我们察觉和把握的。也就是说,科学是永远无法研究人的自由意志的。问题完美解决。

不知道为什么我觉得跟佛教的理论很类似,佛教里面讲真俗 二谛,所谓的真谛就是我们无法描述无法理解超越理性的绝对真理,而俗谛就是我们轮回中的相对真理。

比如空间和时间的概念,就是人在学习一切知识前,必须先具备的先天认识形式。康德给出了几种证明方法,我们说两个简单的:第一个证明是,我们有感觉对吧,而“感觉”暗含的意思是,我们感觉到的是“我们之外”的东西。不用人教,就知道自己有意识,自己的意识之外还有一个世界。这“之外”两个字,就说明我们有空间概念。换句话说,如果我们没有空间概念,我们的感受就是一片混沌,连什么感觉是属于自己的、什么感觉属于外界的都不知道。自然,在这种状态下,我们也不可能再去学习空间的概念。所以空间这个概念是先于经验的,而且是每个人必有的。第二个证明是,人类可以想象不存在物体的空间,但是不能想象不在空间中的物体。这说明空间是不依赖外界经验存在的概念。同样的道理,时间概念也是先验的。

佛教里面讲涅盘也是超越时间空间看概念的。

假设出了一个交通事故,有一个警察去调查,调查回来说:这个事故不是在任何时间发生的,也不是在任何地点发生的,也没有任何发生的理由。那么警察局局长一听肯定气疯了。哪怕这警察胡编一个时间、地点和理由,警察局局长也不会那么生气。为什么呢?假如警察胡编了时间、地点和理由,好歹我们有机会知道他说的是真话还是假话。但他这个没有时间、地点和理由的报告呢,对我们来说是完全不能理解的。我们根本就没法理解这么一句话。这说明一个知识如果不具备时间、空间和因果律的要素,我们就完全不能理解。也就是说,只要我们有关于某物的知识,这知识必定伴随着时间、空间和因果律等等。时间、空间和因果律的概念是先于我们的经验而具备于我们思维中的。

佛教里面说,我们甚至无法想象不在时间和空间之内的事物。所以涅磐这种超越时空的境界只能通过修行亲自证得,不能靠理智来理解,也没有办法传授,因为我们无法用语言描述,因此我们只能用相对的方法来逼近这个真理。

康德的解决方法是,他把世界分成了两个部分。一个部分完全不可知,另一个部分则可以用理性把握。不可知的那部分因为永远不可知,所以对我们的生活没有什么影响。只要我们在可把握的世界里生活,理性就又恢复了威力。这样,既没有破坏休谟的理论(想破坏也没那能力),又让人类重新信任理性,重新踏实了。康德的学说并不是和我们完全无关的玄学,而是有很重要的现实意义。假如我们接受康德的世界观,我们就同意,这世上总有一些东西是我们无法认识的。我们只要安于在能认识的世界里生活就对了。

确实很有用处。我又联想到了佛教。

康德的哲学工作并不仅仅停留在构建一个新世界观上。康德还有一套伦理观,他觉得也很重要。就像康德的名言:“有两种东西,我对它们的思考越是深沉和持久,它们在我心灵中唤起的惊奇和敬畏就会日新月异,不断增长。这就是我头上的星空和心中的道德定律。”然而我觉得,这部分伦理观对于今天我们这些普通人的意义并不大,我只说一个有趣的地方。康德的伦理学是建立在他的形而上学基础上的。康德认为,人的道德也是存在于先天认识形式中的,因此所有人都要受到道德律的支配,这是无条件的。这样,康德给每个人都必须遵守相同的道德找到了哲学上的根据。同时,因为康德这里的道德是先验的,因此它必须发自内心且不受外界的影响。所以,我们说,如果一个人做好事是为了得到表扬,在康德这里就不算道德。

康德那段话很有名。

康德身体不太好,有几年,他每个月都要向当地警察局询问死亡统计数字,以便估算自己的寿命。但是康德又不信任医生,就自己规定了很多古怪的守则,而且严格遵守。虽然这些守则有些非常怪,但事实证明康德是很成功的,在那个医学不发达的年代,他活到了八十多岁。都有什么怪规矩呢?——康德觉得吃药多了对身体不好,他就自己规定,无论医生怎么说,一天最多只吃两片药。为了避免伤风,他还规定在除了夏季之外的季节里,自己在散步的时候不和任何人说话。他规定自己每天只抽一烟斗的烟,但是据说他的烟斗一年比一年大。他讨厌出汗,一旦发现自己要出汗,就静静地站在阴影里,好像在那等人似的,直站到要出汗的感觉消失。他还在一本小册子中介绍自己在睡觉时对抗鼻塞的招数:“紧闭双唇,迫使自己用鼻子呼吸。起初很吃力,但我不中止、不让步。后来鼻子完全通了。呼吸自由了,我也就很快睡着了。”对抗咳嗽呢,“方法如下:尽最大的力量将注意力转移一下,从而阻止气体喷出”。其实用一句话就可以概括:有症状就硬憋着。这都是什么治病方法呀!

突然觉得这老头很可爱。

第二十五章 王者之风

形而上学家们认为自己研究的是世间万物的本质,黑格尔也是如此。他认为他的形而上学并不是在书斋中的空谈,而是能解释世上所有事物的发展规律的。他说一切事物都在绝对精神的统御下,朝着进步的方向发展,包括整个人类的历史也是这样。因此,黑格尔说:“凡是合乎理性的东西都是现实的,凡是现实的东西都是合乎理性的。”按照我的理解,这里的“理性”指的是绝对精神。“现实”指的是符合历史必然性的事物。这句话的意思是,所有合乎绝对精神的事物,必然会发生。说白了,意思是历史一定会按照绝对精神的要求前进,不会例外。

划重点,期末考试要考的。

黑格尔说世间万物的发展一定要符合绝对精神,因此他的绝对精神观是决定论的,他认为历史不是人类创造的,也不是个别事件的堆砌,历史有自己必然的进程,我们人类只是历史实现自己目的的工具。就算拿破仑那样的伟人,其实也是绝对精神的工具。并不是拿破仑自己要征服欧洲,而是绝对精神要利用拿破仑来推进历史的进程。所以黑格尔表面上赞扬的是拿破仑,其实是在赞扬绝对精神。

黑格尔的历史通向哪里呢?黑格尔的历史通向绝对精神。他认为宗教比自然科学更高级,哲学又比宗教高级。最后,绝对精神会通过哲学完成自己的发展,达到最完美的境界。具体说来,那个被绝对精神决定了的、借以认识世界、实现绝对精神的人是谁呢?黑格尔说:就是我!就是爷本人!黑格尔认为自己揭示了整个世界的规律,他已经说明了这个世界的本来面目,完成了绝对精神自我实现的任务。黑格尔的哲学就是一切哲学的终结,哲学发展到他这里就到头了。

不知道黑格尔是不是哲学家里面最会吹牛逼的一个。

黑格尔认为世界一切事物的发展都要符合辩证法,这个看法太教条了。但有时辩证性的确有道理。比如,一个人是怎么成长的呢?一个人先有一个原有的思想(正题),然后在生活中遇到了这思想不能解决的问题(反题),思想和现实问题发生了冲突,才会引起他反思人生。这个反思的结果不可能说最后完全不顾以前的旧想法(正题),最后的新思想(合题)肯定是结合了正题和反题。这就代表着人变得更成熟了。辩证法还说,所有的正题都有反题,这提醒我们要把事物和它的对立面放在一起综合来考虑。用术语来说,当我们看到一个现象的时候,光孤立地看这个现象,那样的层次会比较低。假如我们找到这个现象的反题,再把正题和反题合在一起,分析正题和反题之间对立与统一的关系,从而观察到它们的合题,那我们看事物的能力就能提高一个级别了。

您应该会同意,我们追求个人幸福的最高境界并不是有钱有权有一大堆情人围着,并不是肉体享乐。哲学史上也没有哪个哲学家认为纵欲是快乐之道。连古希腊的享乐主义者追求的也不是肉欲的极限,而是适度的享乐、劳逸结合的生活。这是因为大家都发现一个问题,肉欲快乐固然很好,但是纵欲总是和它的反题——痛苦、空虚紧紧连在一起的。不存在某种只给人快乐、不带来痛苦的享乐。这正符合了辩证法的观点。所以最后的结论就是,我们追求个人幸福的最高境界,不是纵欲,而是内心的平静。

涅盘寂静。

第二章 悲观主义

休谟说我们的理性根本无法认识这个世界的真相。康德点点头:休谟说得没有错,我们的确无法认识物自体,可是我们生活在表象的世界里。在这个表象世界里,一切都先经过先天认识形式的加工。而先天认识形式我又用理性给分析得清清楚楚了,那表象世界还是被理性统治的呀[插图]。所以在康德看来,理性就是我们这个世界的统治者。没错,理性确实管不了物自体,但是物自体也不影响我们的世界呀。叔本华说,不,物自体能影响我们的世界。不仅能影响,而且影响力超大,我们用理智控制不了。在康德那里,这个世界的基础是井井有条的理性。在叔本华这里,这个世界的基础是无法控制的生命意志。因此康德对世界的看法是乐观的。叔本华对世界的看法是悲观的。

叔本华说,全宇宙的生命意志只有一个,这让人想到了斯宾诺莎的实体论。斯宾诺莎说,世界上所有的事物都属于同一个实体。但是斯宾诺莎的理论里,实体是完满至善的,所以我们每个人都因此得到了幸福。而叔本华说,生命意志是邪恶的,是痛苦的源泉。所以每个人都逃脱不了痛苦。叔本华为什么这么说呢?满足欲望会带来快乐,这没错。但是叔本华认为,欲望本质上是痛苦之源。因为满足不了欲望,人会痛苦。满足了欲望,人又会产生新的、更高的欲望,还是会痛苦。叔本华打比方说,满足欲望,就好比施舍给乞丐一个硬币,维持他活过今天,以便把他的痛苦延续到明天。叔本华还引用一句法国谚语,说明人们无止境的欲望:“更好是好的敌人。”如果人满足了全部的欲望,而且没产生新的欲望,人会幸福吗?不会,人会感到空虚和无聊,这也是痛苦。所以快乐只是暂时的,痛苦才是永恒的。人生就好像在痛苦和无聊之间不停摆动的钟摆。这种情景就像王尔德说的:“人生有两大悲剧:一个是得不到想要的东西,另一个是得到了。”而且前面还说过,生命意志还是盲目的、永不疲倦的。可想而知,人类被这么一个没法打败又只能带来痛苦的东西控制,那人生自然是一个悲剧。我们记得,康德费尽千辛万苦,才给人类找回了自由意志。而在叔本华这里,自由意志又没了。

在佛教里面叔本华的生命意志有另外一个名字,叫做轮回。不过在佛教里面,轮回并不是最本质的,超脱轮回之后的境界才是最本质的,它的名字叫做涅磐。

叔本华认为,基督教教义中的原罪就是生命意志,基督教鼓励的赎罪精神就是要求人们去克服生命意志。所以基督教比其他宗教在欧洲更受欢迎,因为它认识到了生命意志的悲观主义精神,而其他宗教都是乐观主义的。佛教比基督教更重视禁欲。基督教在某种程度上并不禁欲,比如鼓励人多生养,鼓励人勤奋工作。佛教不同,佛教认为欲望是痛苦的来源,主张彻底摒弃一切欲望。这和叔本华的观点很像。这不是巧合,叔本华的哲学观点深受印度佛教的影响。据说他的书桌上经常摆放的是一尊康德像和一尊佛像。

果然如此。

悲观主义对于我们来说仍旧有现实意义。首先,叔本华的悲观主义从某些角度上看确实是成立的。虽然说理性未必就会败给欲望,但对于大部分人来说,欲望的确是生活的主题。我们是为了获得尽可能多的安全感,为了有更好的物质享乐,为了和别人攀比,才会去忍受无穷无尽的艰辛劳动和在各种挫折中的垂头丧气。大部分人这一辈子活着,为的都是满足各种各样的欲望。我们也同意,欲望是永远不会被满足的。满足了就会产生新的欲望,不满足就会产生饥渴感。所以叔本华的世界观对于大部分人来说,是对的、没问题的。叔本华提出的解决方案也没问题:既然满足欲望是一条不归路,那我们就应该早点看清这一点,不再去满足欲望。但欲望不满足我们会饥渴痛苦啊,那就可以像叔本华建议的那样,用无关欲望的对艺术品的欣赏来获得暂时解脱。这也是被社会普遍接受的生活观。人发财了,整天酒池肉林,追求物质享乐,并不是个长久的选择。不管多好的享乐,玩一阵子就会觉得没劲了,感到空虚无聊。一些有钱人想不明白满足欲望是条不归路,还在不断追求更强烈的刺激,满足更大的欲望。可是享乐的标准不断水涨船高,每一次获得相同快乐所需要的金钱越来越多,最后总有一天会捉襟见肘,把自己玩穷。同时,人的精神和肉体所能承受的刺激也是有限的。不断地追求更多的刺激,最终只能是找死——赌钱、飙车、冒险、吸毒。但是欲望还是不会停啊,再往前走,就只有自我毁灭一条路了。另一种有钱人,当他们玩一玩、发现纵欲也不过如此以后,他们的业余生活就不再追求物欲,而是改成追求艺术。这是古往今来很多有钱人的选择。

在佛教看来悲观主义过于强调苦的一面,所以毕竟还差了一层,在佛教看来,苦是可以解脱的,四圣谛里面的道谛就是解脱的道,我们每个人的本性里面就包含着解脱的道,我们每个人都有可能达到涅盘寂静的解脱境界,相比于悲观主义,佛教更有希望。

悲观主义能给我们带来很大的安慰。悲观主义让我们把整个世界都看成是一个很差的地方。那么,我们就不必对这世界期待太多。当这世界损害我们的时候,我们也不会感到不公,或者觉得很失望。同时,我们对这世界的期待少了,我们自己的生活压力也就小了。因为人生再怎么折腾也是悲观的,那我们何必要一味去奋斗?比如,如果相信叔本华的理论,你会觉得,无论挣再多的钱、获取再高的社会地位,得到的仍旧是不能满足的欲望和空虚。这不比混吃等死好到哪去,反而还会因为追名逐利而放纵了自己的欲望,让自己更加痛苦。这么一想,也就没有什么生活压力了。悲观主义的另一个好处是,他能让你意识到世界上的其他人和你一样注定痛苦,无论那人多么有钱多么风光也是一样。那么相比之下,自己的痛苦也就会好受一点。嫉妒和憎恨是一般人难以摆脱的痛苦之源,当你意识到你所嫉妒或者憎恨的人也注定摆脱不了悲观世界的时候,心里也会好受多了。

第四章 瞧!这个人

叔本华,一般人以为他是一个悲天悯人的慈祥老头。不!生活中他暴躁刻薄。尼采,一般人以为他是一个放荡不羁的狂人——不,生活中他是一个温和的智者。

凡是我们不理解的人,都当作是精神病算了:唯心主义是精神病,怀疑主义是精神病,尼采是精神病,一切哲学家都是精神病。当你在书店里眼睛扫过那些看不懂标题的书脊,心中是否在想:他们肯定都是故弄玄虚的骗子、自找麻烦的呆子,他们的书既看不懂也没有用。这样的世界才简单、才可爱嘛。

令人悲哀的是我们从小受的教育就是这样子,没有试着去理解那些难以理解的人,就用一套很落后的马克思主义学说来统治我们的思想,其实马克思主义在西方早就被驳的体无完肤。

第五章 钢铁之躯

尼采继承了叔本华的形而上学。叔本华说物自体是“生命意志”,尼采给改造成了“权力意志”。“权力意志”一词中的“权力”容易引起误解。这并不是政治权力的意思,而是指要让自己变得更强大、更强壮、更富创造力的欲望。尼采把人分成了强者和弱者。强者体现了权力意志,他们的特征是积极向上、勇于进取、勇于牺牲、善于创造。弱者相反,特点是胆小、保守、善妒、虚伪。传统欧洲人相信基督教的普世精神和卢梭的人文主义,两者强调的都是对弱者的关怀,强调人人平等。尼采不同意。他认为,同情弱者没错。但弱者不能以此为理由,去要挟、榨取强者,去拖强者的后腿,这样做是可耻的。打个比方,强者看待弱者,就跟人类看待猿猴一样。猿猴对人类有用吗?如果不关在笼子里而和人类混居,那一定会给人类添乱。强者眼中的弱者也是一样。对弱者不应该光是怜悯,还应该限制他们的能力,免得他们给强者捣乱。

不知道为什么,我感觉对我很有吸引力。

尼采把道德分成了两种。他谈的第一种道德是属于弱者的道德,尼采叫它奴隶道德(又叫“畜群道德”)。表面的内容是同情、仁慈、谦卑、平等。其实本质上,是弱者为了掩盖自己对强者的恐惧、嫉妒和自私,借助奴隶道德去限制强者。比如现在社会的道德,大部分都是禁止型的命令,如“不许占有别人的财产”“不许欺骗”。这些禁令,保护的不是强者——强者不会让自己的财产被人占有——保护的是那些不能保护自己的弱者。弱者对强者感到恐惧,因此奴隶道德强调“仁慈”“谦卑”,把强者和特立独行的人看作是危险人物,要求社会限制他们的能力。弱者又因为自私就强调“同情”“分享”,要求强者给弱者分一杯羹。我们现在都讲“人人平等”,尼采却反对平等。他认为平等主义者本质是嫉妒成性,看到别人有什么,他们就也想要什么。实际上我们细想,这个所谓的“奴隶道德”,不就是我们人类社会的传统道德吗?所以尼采说:“迄今为止用来使人变得道德的一切手段都是不道德的。”尼采说的第二种道德是强者的道德,它可以叫作贵族道德。这种道德鼓励人们积极进取,特立独行,崇尚强大,鄙视软弱,追求创新,拒绝平庸,它代表了生命积极的一面。尼采认为,奴隶道德和贵族道德最明显的区别在于:奴隶道德总是在禁止,不许人们做这做那;贵族道德则是在鼓励人们自由创造。尼采并不完全反对奴隶道德,他反对的是把奴隶道德强加在强者的身上,他认为这会限制人类的发展。那有人说了,尼采的道德观不是会造成弱肉强食吗,不是会造成强者欺凌弱者吗?尼采的回答是,人的本性就是残忍的。

感觉很有道理啊。

尼采的道德观和基督教道德有明显的矛盾。在尼采生活的社会里,基督教道法就和咱们这里的儒教道法一样,是全社会广为接受的道德规范。《圣经》里说什么事情是善的,那全社会的人都不用多想,都认为这件事是善的。但尼采认为,基督教道德是典型的奴隶道德,本质是伪善的。基督教鼓励人们变得谦卑,其实就是鼓励人们做弱者。所以尼采大喊“上帝死了!”意思是,他想去掉上帝。如果没了上帝,人们也就不需要无条件地遵守基督教道德了。尼采反对人人平等,这和法国大革命也有一定的关系。在法国大革命中,带着民主之名的雅各宾派进行了恐怖统治和血腥屠杀,这让很多欧洲思想家看到了“多数暴政”的危险。在尼采看来,最聪明、最有创造力的人在这个社会里是少数,庸人总是多数,而原始的民主模式总是要少数人听多数人的话,这就等于让少数的聪明人屈服于庸常的大多数。尼采推崇强者,可是尼采发现,大部分强者都被奴隶道德压抑着,不能摆脱弱者对他们的束缚。因此,尼采希望“超人”出现

原来上帝死了是这个意思。

“超人”这个词在尼采的理论里不是指拥有强大权力的人,不是说这人一定要当总统、当将军,而是指能够完全按照自己的意志行动、能充分发挥自己的创造力,并且能摆脱奴隶道德、不被弱者束缚的强者。超人是尼采对人类的一种理想,在尼采眼里,整个人类历史里也很少有人能成为真正的超人。尼采和叔本华一样,认为这世界是悲观的。但他的解决方法和叔本华不同。尼采的世界观带有强烈的激情,他认为叔本华的禁欲是胆小者的逃避行为。他觉得人不应该像叔本华宣扬的那样避免痛苦,而是应该承认痛苦,迎战痛苦。简而言之,尼采推崇的是一种精英主义。

在尼采看来,人类的知识,如形而上学、科学理论,都是理性的,可是作为世界本质的权力意志是非理性的,因此这些理性知识也不是真正的真理,只是权力意志构造出的假象而已。权力意志为什么要构造这些假象呢?权力意志是征服的意志,在权力意志的驱使下,人类去研究世界不是为了简单地求知,而是为了能更好地控制世界。比如,人研究世界就要给世界下定义,这些定义是人强加给这世界的,这便是权力意志控制世界的表现。既然人类的知识只是权力意志用来控制世界的工具,那么也就根本不存在什么真理。人们追求所谓的真理,只是因为人们需要合乎真理去征服世界。所以尼采说:真理就是一种如果离开它、某种生物便不能活的错误。换句话说,在尼采看来,所谓的真理和错误的区别是:真理有用,错误没用,甚至有害。比如因果律的问题,尼采的解释是,根本就没有因果律,相信因果律是因为我们不相信它就没法生活。尼采不是传统意义上的形而上学家,他的作品里没有严格的论证和推理,形式很像散文,不像是理论,更像是箴言、宣言。尼采对于西方文明的价值,不是他提出了一种新的形而上学,而是摧毁传统的西方道德观。在尼采那个时代,基督教道德是极难被撼动的。尼采喊“上帝死了”在当时是颠覆性的。如果尼采只是随便喊一喊,那他就只是一个哗众取宠的小丑。问题是,他的观点还蛮有说服力的,听上去很有道理。因此尼采起到了振聋发聩的作用。

第八章 人猿星球

进化论的关键内容有这么几条:第一,生物的基因信息可以遗传给下一代:第二,在遗传的时候,基因会发生不可控制的随机变异;第三,整个生物种群都面临着巨大的生存压力,每一代新生物的数量却大于自然资源能够供养的数量,因此每一代新生物中的大部分都会死掉。第四,生物后天的变化在大部分情况下不能改变基因。生物进化的过程是这样子的:因为每次遗传都会产生一系列变异,因此每一代新生物都会有一些个体的生理特征不同于父母辈。或者说,总有些个体长得“怪”。又因为生存压力特别大,每一代里的大部分都会死掉,因此假如这些长得“怪”的地方正好能适应当时的环境,那么拥有这些“怪”基因的生物就有更大的概率存活下来,这些“怪”基因也会保留下来,从而成为这个生物基因中的一部分,生物就完成了一次微小的“进化”。

很精妙的概括了进化论。

误解一:进化论就是生物“从低级到高级”的“进化”。实际上,“进化论”这个词不太准确。更准确的叫法应该是“演化论”。进化论的意思仅仅是,基因中那些适合环境的部分被保留下来了,不适合的部分被淘汰了。这中间并没有高级和低级之分。有些人会觉得从单细胞生物进化为人类,是从“低级”生物“进化”到“高级”生物的过程,人类比单细胞生物更“高级”。然而,人类的机体构造虽然比单细胞生物更复杂、人类的智慧比单细胞生物更高,但这并不一定就是进化的方向。假如“高级”生物指的是更复杂的生物体,并且进化论是“从低级进化到高级”的话,那么经过几千万年的“进化”,为什么今天还会有细菌、有昆虫呢?为什么比细菌构造更复杂的恐龙反倒灭亡了呢?其实,为了生存,有很多生物的构造从复杂演化为简单。达尔文在《物种起源》书里画了一张插图,画的是一棵巨大的树,树根是原始生物,越往上树的分叉越多,生物越复杂。这张图暗示了生物是从低级到高级“进化”的,生物越进化,构造越复杂。这是《物种起源》里唯一一张插图,我们的课本上过去也有这张图,然而这张图是错的。目前生物学界更喜欢的画法是把所有的生物画成一个圆形,越靠圆心的生物在地球上生存的时间越早,人类和今天所有的动植物平均分布于圆形的边缘,看不出谁比谁更高级来。这幅圆形图的意思是说,不管生物的构造是否复杂,大家都是演化的幸存者。

确实有这个问题。

误解六:进化论还只是一种未经验证的假说。反对进化论的宗教人士特别喜欢这么说。但有两个解释可以反驳、否定进化论的倾向。第一,所有的科学理论都是一种假说。这就是为什么我们前面要费这么大力气解释进化论的细节,我是想让大家明白,达尔文进化论是目前最合理、佐证最多、反证最少,也是最简洁、最聪明的假说。第二,有越来越多的科学发现增加了进化论的可信度。除了现实中可以观察到活生生的进化过程外,最有力的证据,是在不同地质层里发现的化石都符合进化论的预言。另一个有力的证据是人工培育。我们今天接触的大部分生物,粮食、蔬菜、瓜果、家畜、宠物,全都是人类选育出来的。今天的小麦也好,家猪、宠物狗也好,放到野外去就都不可能生存了。人工选育的过程是加速版的进化论,是进化论的应用和证据。在生物研究中,我们还会发现很多“笨拙”、无用的设计。假如生物真的是由一位最智慧的神灵设计的话,这些地方本可以被设计得更好,这也可以反驳“神创论”。

无论是天性自私论,还是社会达尔文主义,全都犯了一个错误,那就是把“我们为何成为这样”和“我们应该怎样”等同在一起。这些观点的逻辑是,既然人类的基因是经过生存斗争而来的,那么人类就应该把这种斗争精神延续下去,继续通过竞争来筛选基因。但是,为什么呢?进化论仅仅阐述了一套基因变化的规律,这中间并没有任何道德含义。而且正是进化论把神创说从生物界赶走了,才把生物学中的道德元素降到最低的程度。在整个进化论学说中,有任何的观点能说明,进化论是道德的吗?是高尚的吗?是人类不可干涉的吗?实际上,就像人类利用力学改造自然一样,人类早已在插手生物的进化过程。这才有了不适合野外生存的家畜,才有了农作物。所以,把进化论的观点和道德连接在一起,是思维混乱的表现。

说的好。

第十章 寻欢作乐

最后再说罗素干过的一件狠事。在罗素的年代,代数一直缺乏一个像几何那样逻辑完备的体系。因此数学家们创造了一个“集合论”,想给代数一个完备的公理体系。这是人类理性的一大胜利,引来当时数学界一片欢呼。然而罗素琢磨琢磨这事儿,在不到30岁的时候提出一个“理发师悖论”。大意是说,在一个小镇上,有一个唯一的理发师。他理发的规则是,只给“不给自己理发的人”理发。那么,这个理发师该不该给自己理发呢?就陷入了矛盾。我们不细究这个悖论了,简单来说,罗素用这个悖论说明了集合论的一个无法解决的缺陷。

这是一个很有名的悖论。还有很多变种。比如说。我现在说的这句话是假话。

最后再说罗素干过的一件狠事。在罗素的年代,代数一直缺乏一个像几何那样逻辑完备的体系。因此数学家们创造了一个“集合论”,想给代数一个完备的公理体系。这是人类理性的一大胜利,引来当时数学界一片欢呼。然而罗素琢磨琢磨这事儿,在不到30岁的时候提出一个“理发师悖论”。大意是说,在一个小镇上,有一个唯一的理发师。他理发的规则是,只给“不给自己理发的人”理发。那么,这个理发师该不该给自己理发呢?就陷入了矛盾。我们不细究这个悖论了,简单来说,罗素用这个悖论说明了集合论的一个无法解决的缺陷。

理发师悖论很有名。

第十一章 快乐王子

维特根斯坦发现用不着读完书,只要交一篇论文就可以获得哲学博士学位,于是他就把那篇《逻辑哲学论》交了上去。负责审阅该论文的是摩尔和罗素。摩尔也是个有名的哲学家,当年给维特根斯坦当过老师。维特根斯坦上大学的时候水平就比得上罗素了,而且这篇《逻辑哲学论》已经成名多年,被当时很多哲学家当作经典阅读,你说摩尔能怎么评价呢?自然,他说这篇论文是“天才的作品”,水平已经超过了剑桥哲学博士学位所要求的标准。论文答辩那天,罗素和摩尔一起走进考试的房间,罗素微笑着说:“我一生从未有过如此荒谬的事。”在正式答辩之前,维特根斯坦先跟罗素和摩尔闲聊了半天。聊到后来,罗素跟摩尔说:“咱还是答辩吧,你好歹也得问他几个问题,怎么说你也是教授啊。”答辩的时候,罗素对维特根斯坦的一个观点产生了疑问。维特根斯坦解释完了,然后拍了拍两个老师的肩膀说:“别介意,我知道你们永远都搞不懂我在讲什么。”维特根斯坦这么说不仅因为他确实牛,也因为他和摩尔已经很熟了,都是好朋友。当年维特根斯坦在剑桥上学的时候,摩尔还把自己在剑桥的房间让给了他。所以摩尔回忆这次答辩的时候,说这件事“既愉快又可笑”[插图]。

第十二章 逻辑实证主义

逻辑实证主义者想得不错,他们要发动一场继承苏格拉底、笛卡尔和休谟怀疑精神的运动,他们要用逻辑工具去一一考查所有的哲学命题,把所有没有意义的、不可证实的命题都剔除出去。然而工作的结果却吓了他们一跳。他们发现,剔除到最后,只剩下了两种命题。一种是重言式命题,就是类似于“桌子是桌子”这样的话。这样的话当然是绝对正确的,可是这样的话不包含任何有用的信息,不过是文字游戏而已。之前的形而上学家们找到的那些所谓终极真理就多半属于此类。因为重言式命题绝对正确,所以就会被误以为是终极真理。这些终极真理一点用也没有。还有一种命题,是类似“这朵花是红色的”之类描述片段经验的命题。虽然是新知识,但是无法形成普遍真理,也就无法回答哲学问题了。其他的哲学问题,特别是形而上学问题,全都是没有意义的伪问题。要么违反了种种逻辑规则,要么无法用经验去实证。这样一来,逻辑实证主义者就回答了一个问题:为什么哲学家们对形而上学争论了那么久都没有结果呢?因为他们争论的全都是没有意义、不可能有答案的问题。人们没办法靠实证的方式来解决这些问题。

年轻的维特根斯坦写了一本《逻辑哲学论》,完成了逻辑实证主义的工作。写完这本书以后,维特根斯坦以为自己解决了所有的哲学问题。语言都被他用逻辑工具分析光了,他觉得所有用语言能表达的句子他都明白是怎么回事了。所以维特根斯坦说:“凡是可说的事情,都可以说清楚,凡是不可说的事情,我们必须保持沉默。”对这句话我的理解是:凡是符合逻辑实证规则的语言,内容都很清晰准确;凡是不符合逻辑实证规则的语言,说了也是没意义的,就不用说了。这么一来,维特根斯坦觉得他没有困惑了,就去乡下当小学老师了。但是随着时间的推移,他逐渐觉得不是这么回事儿。维特根斯坦在剑桥当老师时他学生的课堂笔记,以及他留下的那些思考笔记,都在他死后被他的学生整理集结成书出版了。这本书显示了维特根斯坦在《逻辑哲学论》之后的哲学思想和逻辑实证主义完全不同。

维特根斯坦发现,语言并不能只停留在表面的逻辑分析上。同样的一句话,说话的情境不同,说话人的语气、表情、手势不同,常常会表达出不同的意思。换句话说,每一个情境都给语言制定了不同的规则,语言得和规则结合在一起,才能显示真正的意思。而这规则又是没有逻辑可言的。维特根斯坦揭示的,其实是理性思维和现实的矛盾。逻辑实证主义的理想很好,要坚持绝对的理性、绝对的正确,可是最后发现,这个绝对的理性却得不到任何有意义的结论,连一个普遍的理论都得不出来。可是另一方面,我们的现实生活是多姿多彩的,我们有灿烂的文化,有日新月异的科学知识。这说明了什么?说明理性根本无法担负从总体上解释世界、指导生活的任务。维特根斯坦建立的逻辑实证主义,又被他自己亲手毁灭了。可以说,维特根斯坦是世界上独一无二的、能够提出两种截然不同又都对哲学史影响深远的理论的哲学家。所以我们前面才说,他是史无前例的双倍哲学家。

第十四章 终结形而上学

波普尔看出了其中的问题,提出了一个检验科学理论的重要标准:证伪。什么是科学理论,什么不是?其中关键的标准,是看这个理论有没有可以被证伪的可能。具体来说:科学理论必须能提出一个可供证伪的事实,假如这个事实一经验证,便承认该理论是错的。如果暂时没有人能证明它是错的,那它暂时就是真的。比如“所有的乌鸦都是黑色的”,这就是一个可证伪的命题。这等于说“只要你能找到一只不是黑色的乌鸦,就能说明这个命题是错的”。既然我们尚未找到不是黑色的乌鸦,那么到目前为止这个命题就是暂时正确的。换句话说,所有的科学理论都是一种假说,科学家没有办法证实任何一种科学理论[插图]。但是科学理论可以给别人提供验错的机会。在没被检验出错误之前,我们就姑且相信这个科学理论是正确的。

证伪主义有点像是科学理论上的进化论。在形而上学统治的科学观下,人们认为存在着一个绝对真理,我们在形而上学的指导下,可以带着科学大踏步地朝着这个真理前进。证伪主义的科学观是,人类提出的各种科学理论有点像是基因突变,科学家们发散思维,想出各种充满想象力的假说。证伪就如同自然环境对基因的筛选,经不住证伪的假说都被淘汰,留下的都是经得住检验的,也就是暂时正确的科学理论。那些留下来的理论,科学家们也在不断地尝试证伪,一旦证明是错的,就进行修改。这样科学理论就会越来越完善。这个试错、修改、完善的过程是无休止的,科学也因此会越来越接近真理[插图]。概率主义认为,我们每一次检验科学理论正确,都是在为科学作贡献。证伪主义认为,检验正确并不为科学作贡献,只有检验出科学理论是错的,才是真正为科学作贡献。

第十四章 终结形而上学

现在,世界大部分国家的刑事司法都接受“无罪推定”原则。意思是说,假如没有足够的证据证明一个人是犯罪嫌疑人,那么就应认为他是无罪的。为什么要坚持这个原则呢?除了人权精神外,还可以用证伪主义来解释。如控告某人参与了一起诈骗,如何证伪这句话呢?首先,被告人必须找出被控告这段时间内的所有活动细节,从而证明自己没有和诈骗团伙有过联系。且不说和团伙有过联系又没参与诈骗的人该怎么说吧,就说真没联系过,他又该怎么向法庭彻底证明这一点呢?证明自己没出过门、没见过犯罪嫌疑人?那你有没有可能用电话联络?你没用电话,那有没有用过电子邮件?电子邮件没用过,那你用没用过飞鸽传书,用没用过烽火?你如何证明自己没有在被控告的时间内使用过烽火?找来邻居证明你们家那几天从来没冒过烟吗?邻居说我中午打了一个盹,没看见,那你就算有罪啦?“某人犯过某罪”不可证伪。相反,“某人没犯过某罪”,这个命题是可以证伪的。只要找到他犯罪的证据就可以推翻这个假设了。因此,在都没有充足证据的情况下,在两个命题中,法院只能采信可证伪的后者,而不会采信前者。前面还提到过“判例法”。在该制度下,法律不是一成不变的,而是每一次审判都会对法律的解释进行修改,法律在审判中不断改进自己,以便更适应新的社会环境。这也符合证伪主义“没有绝对的真理,理论需要永远不停改进”的真理观。

想想三法印,都是可以证伪的。第一条诸行无常,如果找到了一个能够永恒不变的事物,它就被证伪了。第二条诸法无我,如果找到一个事物,不是由其他事物构成的,而是有一个永远不变的自我构成的,它就被证伪了。第三条涅槃寂静,如果有圣人达到涅槃,而不是处于寂静的状态,这一条就证伪了。所以佛教是很有科学精神的。😃

历史主义的逻辑是,既然自然社会存在规律,那么历史也应该有规律。我们历史主义者像科学家一样揭示了这个规律,人类按照我们揭示的规律奋斗就可以了。但证伪主义认为,没有永恒不变的真理,所有的理论都可能是错的。所以,也就不存在什么“历史的必然规律”。而且科学理论未来的发展方向也是难以预测的。就比如在牛顿时代,没人能够预测相对论的出现,也没人能预测牛顿理论将会在哪里出问题。因此,预测未来的历史规律,一劳永逸地设计一种绝对正确的政治制度,也是不可能的。用钱穆先生的话说:“制度须不断生长,又定须在现实环境要求下生长。”波普尔因此主张应当建立“开放社会”,要求执政者能够广泛接受意见,赋予大众质疑政策的权利。因为执政理论和科学理论一样,永远都可能是错的。必须要不断地接受证伪,才能保证理论的正确。这正是现代民主思想的核心精神。我们有的人可能会简单地以为,民主就是“大家一起投票,多数说了算”,就是“少数服从多数”。其实这种原始的民主制度有极大的缺陷,这个缺陷在雅典人判苏格拉底死刑、法国大革命的屠杀、希特勒被民众选上台等事件中已经暴露无遗,早就被现代社会抛弃了。我们常说“人民大众的意见最正确”,这句话对吗?在证伪主义看来,这话就有问题。因为证伪主义认为世上没有绝对真理,那怎么可能说某个意见“最正确”呢?就算全世界99%的人同意的一件事,也不能说这件事最正确。否则,布鲁诺时代就不用怀疑地心说了。证伪主义的政治观,最关心的不是谁制定的政策,而是无论谁制定的政策,都不能成为绝对真理。不管是美国总统下的命令还是全世界人民投票的结果,都要给别人留出修改、推翻它的机会。

中国正在努力的远离开放社会,😂

无论谁被民选上台,也不会给世界造成太大伤害。因为他上台后的个人权力非常有限,哪怕加个税都需要国会批准。他还必须随时面对全国媒体的质疑、随时可能被弹劾、干四年就得重选、干八年就得下台。这制度不能保证总统想出“最正确”的决策,但可以保证一旦总统作出“错误”的决策,举国上下有无数可以阻止它的机会。可以随时“纠错”而不是“多数说了算”,这才是现代民主制度的核心精神。

看来我以前对民主制度的理解是错的,我以前以为民主制度是让民众有选择的权力,就像那个故事里面一样,你是愿意选择一头狼,还是愿意选择两头狮子统治你。

历史主义的另一个问题是,整个人类历史发展所涉及的因素太多,我们无法设计实验,让历史大事件重复发生。因此,哪怕是对已经发生过的历史,很多解释也是无法证伪的。比如有人说:“历史是由人民创造的,假如没有法国人民的力量,拿破仑就不可能成功。”有人说:“历史是由伟人创造的。没有拿破仑,就不会有法兰西帝国。”这两个命题其实都是不可证伪的。因为历史不可重复检验,谁也不能让时光倒流,把拿破仑用飞碟抓走,再重新检验一遍历史。也不可能现在做一个实验,模拟拿破仑时代的所有经济、文化、政治细节,来检验这两个理论。因此这两种观点可以永远吵下去,各自举出无数的间接论据,却全都无法说服对方。所以,历史主义都是不可证伪的空话。历史主义者对未来的预言如果是明确的(如预言某某事件在某个时间段一定发生),那的确是可以证伪的。但这需要理论持有者能勇于承认错误,一旦理论被证伪了就要认错,而不要推脱责任,说额外因素太多,干扰了预言。但事实是,现实中能够干扰历史进程的元素太多了。有很多历史主义的信徒,不断用各种额外出现的新因素解释原有理论的错误。这时,历史主义也就变成不可证伪的了。因为影响历史的因素太多,不可重复实验,因此波普尔认为,科学只能研究局部的社会问题,如某条法律好不好用,某个政策价值如何。因为只有局部问题才可以反复验证。而那些执掌全局的宏观理论,都是不符合科学精神的。

我总算找到我为什么一直觉得宏观经济学是扯淡的原因了,原来是有科学道理的。

第十五章 实用主义的科学

科学是个只讲究实用与否的工具。我们在筛选科学理论的时候,实用是唯一的标准:首位的要求是这个科学理论能够指导我们工作,不能够出错。其次,在不出错的基础上越简单易用越好。就比如牛顿力学其实是错的,但在我们粗糙的日常生活中已经足够用了,我们就只当牛顿是正确的,不需要了解相对论。而且相对论就绝对正确了吗?也不是。相对论也是可以被证伪的。目前没人推翻相对论,只是因为人类现在观测到的物理数据里还没有足够多的和相对论违背的数据。等到人类的观测能力不断提高了,有朝一日违背的数据越来越多,相对论不够实用了,就会有新的理论去代替相对论了。假如你接受这一点,那么可以听听我个人给科学下的定义:科学就是建立在经验主义基础上的、以实用主义为原则筛选出来的、可以被证伪的理论。说白点就是,科学就是我们在一堆科学假设中,挑出一个能够解释已有的实验和观测数据,而且表述尽量简单,而且还可以被证伪的理论。这个理论就是最“科学”的。

第十六章 科学是什么

虽然我们不能把科学当作衡量一切理论的标准,但是我们仍旧有标准可以用。我认为有两个原则必须坚持:第一是经验主义原则。换句话说,理论好用不好用,必须眼见为实,拿出大家都承认的证据来。第二是实用主义。理论还得有实用价值,不实用的理论再诱人也没有意义。

我们选择信不信某个理论是为什么呢?比如,我们为什么需要在民间医学和现代医学之间选出一个更优秀的理论呢?因为我们要治病对吧?我们要的是它的实用效果。所以我们关心的是这两个理论哪一个更实用。假如你去找了一个懂民间医学的人,他给你看完病说:根据我的某某理论,你的病好啦。你会就这么相信他吗?不会吧。你得在以后的日子里观察自己的身体,看自己的病是不是真的好了。要是没好,你就会找他算账去。所以,我们要选择理论,原则必须是实用主义的,依据必然是经验主义的。因此,拿经验主义和实用主义做考察理论的标准,这不是出于科学家的学霸作风,而是出于我们自己的需要。如果我们接受了这一点,回来再看科学:科学坚持经验主义、坚持实用主义,并且完全开放,允许证伪、允许质疑,反对独断论。那么,还有什么研究方法能比科学更好呢?所以我的观点是这样,我们不能说某个理论“不符合科学理论”,就认为它是错的。但假如我们认为“科学方法”指的是“以经验主义为标准,以实用主义为目标,允许别人检验,反对独断论”的话,那么我们就应该相信科学,就可以说:如果某个理论的论证过程“不符合科学方法”,那么它就是不可信的。

以现代医学的标准,我们的传统中医有很多地方是“不科学”的。中医的辩护者们有一个论点,说你们西方人凭什么非要用“科学”的标准去看待我们中医?那是你们的标准,不是我们的。你强行用自己的标准要求我们,这是一种霸权主义。把科学不言而喻当作衡量事物的标准,这不也是一种迷信吗?这个观点我同意。没错,现代西医中的种种理论、观点都不是绝对正确的,现代西医仅仅是我们对人体的众多解释中的一种,绝不是唯一的。特别是对于人体这种极为复杂、经常处于变化中的研究对象,或许中医的确比西医有更大的潜力,这些观点都是没问题的。但是关键一点,我们不能放弃经验主义和实用主义。就是说,如果想证明中医比西医更有效,就必须在大范围内进行治疗实验。目前最好的方式是大样本随机双盲实验。做完实验一统计,对于某个病症,哪种治疗方法的效果更好,我们就选择哪种疗法。说中医历史上有过多了不起的记载,里面有多少五行八卦之类深奥的哲理,中医“能平衡人体”“从整体看待人体”“更自然”等等理由,我认为都不重要。重要的是此时此地,它能更有效地治病,它能切实地延长人的寿命。你能实现了就算你本事,你要是不能实现,你的理论再天花乱坠我们也弃你不用。对于西医来说,我们也同样对待。

最好的检验办法还是“大样本随机双盲实验”。解释一下。“大样本”是说,我们验证某个方法可靠不可靠,光看一个例子是不行的,要找很多很多的例子一起验证。例子越多,实验的结果就越可靠。“双盲”是说,要把实验对象分成好几组。比如实验一个疗法是否有效,不能光看吃了药后多少病人病好了。还应该有另外一组病人,只给他们毫无疗效的安慰剂,对比两组的实验结果,才能知道药物的真正疗效。病人的分组应当是随机的,保证各个组之间患者的情况类似,不能身体好的分一组,身体差的分一组,那样就作弊了。而且这个过程,被实验者和实验者都是“双盲”的。安慰剂的外形和真实的药物一模一样。吃药的患者不知道自己吃的是真实的药物还是安慰剂,目的是避免患者的心理作用影响结果。同时,亲自给患者服药的医生,也不知道哪些病人吃的是真药。这样做是避免医生通过心理暗示(如对服真实药物的患者更关心)来影响结果。当然,最终实验的统计者能准确知道哪些病人吃的是真药,哪些吃的是安慰剂。有的实验做不到“双盲”,也可以只做“单盲”,只瞒着病人。大样本随机双盲实验的作用,在于寻找两件事(如实验药物和疗效)之间真实的因果关系,尽量排除其他因素的干扰。比如,有一些病是可以自愈的,还有很多病只靠心理作用就可以加速痊愈。有很多人一有病就吃药,吃药后身体果然好了。这到底是药物成分的作用呢,还是药物的心理安慰作用呢,还是纯粹是身体自愈了呢?要想搞清楚这些,最好的做法就是找大样本病人,分成三组,一组吃药,一组吃安慰剂,一组不吃药,双盲实验,统计结果。药物有没有用一目了然。

星座算命所给出的结论大多是不可证伪的。但有时也有一些可以证伪的结论,有时也灵,那怎么知道星座到底有没有用呢?找一堆人,随机分组,一组人用和本人相符的星座的预测结果,另一组用和本人不符的星座的预测结果,但也告诉他们这些都是真实的预测结果。看看两组人觉得灵验的比例是否相近。实际上,有人已经做过这样的实验,把同样一段预测结果发给不同星座的人,结果人们都表示很“灵”。双盲实验在这个问题上,排除的是“巴纳姆效应”的干扰。这是一个心理学效应,说的是人们倾向于相信为自己量身定做的、模糊的性格预测,而不管这个预测是不是真的准确。

我们在历史上常看到某些特别灵验的算命大师。如在某名人年轻的时候说此人大有可为,如在朝代更替之前就能预言某朝当兴,假如我们翻开史书统计,灵验的预言比不灵验的要多很多,这说明算命真的有道理吗?不能,因为样本不是随机选择的。要真的检验算命,应该找一大堆算命大师,让他们作出大量可证伪的预言。再叫不会算命的人用类似的句式作一些假预言,成为对照组。看看是不是算命大师组预言为真的概率明显高于对照组,甚至于只要其中有某一个大师的预言概率明显高很多也可以。否则,哪怕名声再大、口碑再好的算命大师,无论历史上的还是当代的,都不能算他真正有本事。双盲实验在这里排除的是“幸存者偏差”的干扰。这个意思是说,只有被验证为正确的预言,人们才会广为传诵,才愿意记录在历史书上。那些出错的预言,人们没兴趣传播。因此光从历史书上或者邻里的传闻里听说的某大师灵验所留下的印象,和现实是有偏差的。

认知心理学真的要好好学一学,我们每个人的心里都充满了偏见。

大样本实验的成本非常高,药物实验耗时很长,很多治疗方法没有条件接受这样的实验。严格如美国的食品和药物管理局,批准新药上市也不是全靠双盲实验,还要结合临床医生的反馈等等更多的信息。但是,这里一定要说但是,对于那些争议特别强的理论,大样本随机双盲实验仍旧是剔除骗术、心理误差、第三方原因等非常态因素来检验争论对象是否有用的最好办法。没有其他任何方法更有效了。说这么多,并不是要具体下结论说中医如何,而是用这个有争议的话题来说明,无论任何声称自己和科学“不是一个系统”、不能“把科学理论强加给我”的理论,最终还是离不开实用主义,离不开经验检验。而且最好的检验办法,就是大样本随机双盲实验。假如你拒绝检验,那么你的理论无论中医还是西医,在大部分情况下就是不实用的,很难和信口开河的巫术、骗术区分开来。

在我眼里中医其实就是巫术或者骗术,鲁迅当年就说过中医都是有意或者无意的骗子。

大样本实验的成本非常高,药物实验耗时很长,很多治疗方法没有条件接受这样的实验。严格如美国的食品和药物管理局,批准新药上市也不是全靠双盲实验,还要结合临床医生的反馈等等更多的信息。但是,这里一定要说但是,对于那些争议特别强的理论,大样本随机双盲实验仍旧是剔除骗术、心理误差、第三方原因等非常态因素来检验争论对象是否有用的最好办法。没有其他任何方法更有效了。说这么多,并不是要具体下结论说中医如何,而是用这个有争议的话题来说明,无论任何声称自己和科学“不是一个系统”、不能“把科学理论强加给我”的理论,最终还是离不开实用主义,离不开经验检验。而且最好的检验办法,就是大样本随机双盲实验。假如你拒绝检验,那么你的理论无论中医还是西医,在大部分情况下就是不实用的,很难和信口开河的巫术、骗术区分开来。

很多国外采风里说,西方人做饭跟做科学实验差不多,跟你学做饭的时候,非要问清楚“少许”葱姜是多少克、“少许”酱油是多少升,厨房里连量杯都用上了。可以说,他们的做菜方式是非常符合“科学”标准的——实际上,好吃又便宜又能快速开分店,不依赖厨师技术还能保持味道不变的西式快餐,就是这么控制质量的。制作菜肴的每道工序都有近似于科学实验的严格数据控制的要求。但是我们都知道,外国人照这种方式做出来的中国菜并不好吃。而一个中国人,完全没有“科学”的学习过程,只靠纯经验的积累,练上一两年就可以做出美味的中国菜来。无论是中国人还是外国人,都喜欢吃中国师傅按照这种“非科学”方法做出来的菜,这就证明,在做中国菜上,中国烹饪法是优于“科学”烹饪法的。实际上,也并没有科学家跳出来冲着中国厨师说:你的做菜方式是不科学的,所以你的菜再好吃也没用,我不吃!这可以证明:科学并不一定就是解释、改造世界的唯一标准。比如在做菜这件事上,科学方法就被打败了。而且我们评价两者孰优孰劣的标准是非常清晰的:立足经验主义的实用主义。中国烹饪法做出来的菜好吃,技巧容易掌握。所以就赢了。

有一种观点,说科学只是众多认识论中的一种,只相信科学,拒绝别的理论,不也是一种迷信吗?什么叫“迷信”呢?不经思考的相信,不允许别人质疑,就叫“迷信”。假如一个人在没学过哲学史的情况下,认为科学代表了终极真理(在哲学中叫作唯科学主义),不承认科学的局限性,认为不能证伪的观点就是错的(我认为,不能证伪的命题仅仅是不可知的),那么这的确可以称作“迷信”。但是,如果他了解科学的局限,仍旧相信科学的结论,那就不叫迷信了。在这点上,我认为科学和宗教、巫术不是对等的,科学比宗教和巫术更“不迷信”一些。关键不仅在于科学理论可以证伪,还在于它的检验是开放的。科学理论的语言基于严谨的逻辑,任何人只要花一点时间学习都能读得懂,科学没有权威(有的宗教教义只有神职人员才有权解释,教众不许有不同的说法),任何人只要有技术条件,都可以去证伪、推翻最权威科学家的理论(有些宗教拒绝教外人士参与讨论,而科学不会歧视人的身份,只要你拿出证据来就行)。因为这些原因,科学虽然有局限,但科学比其他不允许质疑的理论,要更“不迷信”。

以前别人问我只相信科学是不是也是一种迷信,我经常没办法很好回答,以后可以把这段话牢牢记住,然后回答别人了。不过很多时候别人这么问的时候,就已经表示他不想接受你的回答了。他就是要相信别的,比如说中医,比如说宗教,就像那句老话说的,你没有办法叫醒一个装睡的人。不过我相信把这段话好好记住,并且用自己的话讲出来,应该比我以前讲的那一套更有说服力。这里我试着用自己的话讲一遍。如果别人以后问我相信科学是不是一种迷信,我就这样回答他。首先你说什么叫做迷信呢?是不是不加思索的相信。如果这样说的话,那么科学就是最不迷信的。因为科学是没有权威的,同时它是鼓励你怀疑的。它是基于经验主义和实用主义的。科学没有说它自己是真理。它只是说它是最实用最经得起检验的理论。它随时准备着推翻自己。只要有新的证据出来,它就承认自己是错的,然后去接受更新的,能够解释新证据的理论。相比之下宗教巫术或者中医这些东西,要么经不起检验,要么不实用,所以我们说它不科学,我们没有说它是错的,只是说他不科学而已,就是说它经不起检验,或者没有用。比如宗教,你说有上帝,那你让它造个电视机给你看看。比如说巫术,那你让他预言一下明天天气怎么样,有天气预报好使吗。比如说中医,你让它分辨一下什么是病毒,什么是细菌试试。所以科学只是最靠谱的一个理论而已。

世界各大宗教内部都分成很多小派,不少小派之间也互相攻击,互相骂战。为什么宗教总讲“宽容”“慈爱”,而这些细小的派别却难以统一呢?因为宗教理论大多不可检验、不可证伪,因此不同的观点根本无法辩论出对错。过去,教会要靠宗教裁判所这样的暴力机关才能解决争议。可你没听说过科学也有科学裁判所,会把持不同科学结论的人抓起来判刑吧?科学家和其他人类一样拥有各种阴暗面,一些科学家也自私,也互相嫉妒,也会虚伪欺骗。科学家在研究同一件事的时候,也常常各执己见,都认为自己是对的,谁也不服谁,甚至还会有李森科[插图]这种利用政治权力打击异己的恶劣事情发生。但是科学的方法是开放的,因此不同的意见哪怕相隔万里、相隔千百年,也都可以在同一个平台上公平对话。任何人都可以通过实验来发表自己的意见,时间长了,对同一件事检验的次数多了,自然就会分出正误来。当年有多少人不服牛顿,天天和牛顿打架,不久以后,就再没有科学家否认牛顿,因为如果你在否认牛顿的基础上研究,你就不可能搞出任何经得住经验检验的成果来。同样,当年有不少人反对爱因斯坦,过了一段时间后,那些反对的声音也都渐渐没了。教会当年用了成千上万个宗教裁判所、遍地而起的火刑架都没能统一观点。科学家们只靠着几本学术期刊就搞定了。这不是非常了不起的事吗?很多宗教人士都乐于使用汽车、飞机、手机、电话等高科技产品。不为什么,就因为这些东西是最实用的。在一百年前,传教必须靠步行万里路,站在街头扯着脖子喊。现在传教可以用鼠标一点,网页上一发,几十万人都能同时看到。这样方便的技术,为什么不用?我说这些话并不是在攻击宗教,因为使用这些科学产品和世界主流宗教的教义都不矛盾。

这本书真是处处都要画重点。

第十七章 永恒的终结

形而上学走不通,形而上学的问题都没有答案。记住这一句话就够了。我们说过,形而上学的任务,是用理性思维去研究世界本质等“大问题”。形而上学走不通,也就是说,理性不可能回答“世界的本质是什么”“有没有终极真理”“终极真理是什么”“人生的意义是什么”等大问题。硬要回答,答案一定是独断论的,或者在推理上有错误。形而上学家们研究了好几百年,就得出这么一个结论。实际上,所有的形而上学都会陷入无法证明自身的困境。我们说过,经验主义者们的论断“只有来源于经验的知识才是可靠的”,并非来自于经验。康德用来批判理性的工具却没经过自己的批判。黑格尔讲辩证法,但是他的辩证法到最后却并不辩证。尼采说所谓的真理都是谬误,那他自己的理论不也是谬误了吗?逻辑实证主义用来分析语句的规则,经过自己的分析都变成无意义的了。波普尔的证伪主义理论,是不能被证伪的。后来到实用主义的时候,罗素批评说:实用主义以“是否实用”为标准评价真理,但是“是否实用”的标准是什么呢?如此追问下去,必然会形成无限回溯,得不出结论。我们会发现,这种情况在哲学史上不是偶然,几乎每一个哲学流派,都面临着自己不能证明自己的窘境。那么,这种“怀疑者不能怀疑自身”的质问只是一种抬杠吗?如果我们是怀疑者,那我们把原则改成“我们可以怀疑一切,除了本原则之外”不就可以了吗?不行。因为哲学研究的是“什么知识真实可信”的问题,是认识论的问题。按照怀疑精神,任何知识必须先确认是可信的,才能被我们接受。然而,我们用来确认知识是否可信的方法(也就是各种哲学理论),本身也属于知识的一种,它们在给别的知识提出限制的同时,也就是在给自己提出限制。形而上学的任务之一是保证一切知识的来源是可靠的,如果它连自己的可靠性都不能保证,就正好说明它是独断论。

重要的话说三遍。理性不能回答世界的本质是什么。理性不能回答世界的本质是什么。理性不能回答世界的本质是什么。所以对于世界的本质这个问题,我们一定要找到一个立足点,哪怕这个立足点可能是非理性。作为一个佛教徒,我觉得这个问题回答的最好的还是佛教。佛教的三法印都是经得起检验的。一切事物都是无常变幻的。一切事物都是由其他事物构成的,没有永恒不变的本质。超越一切事物的涅槃是寂静的。这三法印里面,前面两条是完全基于理性的,第三条其实是有些非理性的。其实从佛教的观点来说,涅槃是无法用语言来描述的,因为涅盘超越了时间和空间的概念,也超越了理性的概念。如果从理性的角度出发,我们其实无法证明存在涅槃,也无法描述,甚至讨论涅盘。所以我们一定要在某种程度上非理性的相信,涅槃是万物的终点,也是我们的终点,当然这里所谓的终点并不是真正的终点,我只是用了这个词而已。

不靠理性去评价宗教、理学、心学等非理性理论,理论上是不错的,但实际上很难行得通。

神学家奥托说,宗教信徒在强调“神迹”的时候,实际上还是在用理性评价宗教。因为所谓的“神迹”是建立在相信理性的基础上的。违反常规的东西才是神迹,而“常规”是个理性概念。对于非理性的宗教信徒来说,不存在常规不常规,也就不存在所谓的“神迹”。拿佛教来打个比方,假如佛凭空变出了一座金光闪闪的大山,那一般人(包括我)都会跪下来说:“太厉害了,这就是神迹,我信服了!”可是对于真正的佛教徒,他会按照佛教的教义,认为世间一切事物的本质都是超越理性的,是“空”,神迹本身也是“空”,那有什么好大惊小怪的?但是,对于大部分普通人,还是会大惊小怪,还是会很在意“神迹”吧?也就是说,从理论上,宗教信仰的确是超越理性的,可以拒绝理性讨论。但与此同时,包括宗教信徒在内的大部分人,都不可能离开理性思维。如果有一个人,一面告诉别人信教有多少好处(“好处”就是一个理性概念)、告诉别人有多少“神迹”可以作为宗教真实的证明(“神迹”“证明”都是理性概念),与此同时,在辩论不过别人的时候,便用“不能用理性来评判宗教”当挡箭牌,拒绝理性讨论。我认为这样是双重标准,是不妥的。

粗览艺术史不难发现,顶级艺术家都很苦闷。作为这世界上最有才华的人类,顶级艺术家思考的问题常常和哲学家一样,都是一些形而上的终极问题。只不过艺术家不用理性探索,而是想通过艺术作品让别人和自己感同身受。但他们为什么都不约而同地苦闷呢?他们不都是最聪明的人吗?他们不都是在用毕生的精力追求答案吗?原因只有一个:终极问题没有答案,最聪明的人们追求到最后,不约而同地发现这是一条绝路。但正是因为这些艺术家陷于永远无法挣脱的苦闷,而他们又非要倚仗自己过人的天才全力挣扎,所以他们的作品才能深深打动我们。所以世界上才有艺术这东西。

好吧,不知道为什么,我对艺术一直有一种负面态度。可能因为我对艺术是门外汉,也完全欣赏不了艺术,在我的感觉里面,所有的艺术都是装腔作势。我感觉所有的绘画都赶不上照相机,而且我也不太分辨得出来哪些照片照的好,哪些照片照的不好,最多也只能分辨出哪个照片稍微好看一点,以及照片是不是模糊。对于音乐也是一样,我只能大概的分辨出哪些音乐比较好听,一般来说对我来说听得次数多的就比较好听,可能仅仅因为我听顺耳了。文学也是一样,在我眼里所有的文学作品全部消失,这个世界也不会有太大损失,只要保留科技方面的文字就够了。有可能仅仅是因为我是个粗人吧?

第十九章 西西弗的神话

加缪不愿意媒体把自己的名字和萨特放在一起,也不认为自己的观点属于“存在主义”。但是大众基本不理他这茬儿,现在一般人谈到加缪,都认为他的哲学属于存在主义。他最有名的观点是,世界是荒谬的。这是什么意思呢?假如这个世界上有终极真理,那么就意味着在这个世界里有某种高于一切、比任何事物都重要的东西。那么人的存在就是有目的的,目的就是找到这个最最重要的真理,或者按照这个真理的指导来生活。这就是形而上学下的人生意义。过去的形而上学家们,在相信自己发现了真理后,都认为人生是有目的的。比如宗教信徒认为,在教义的指导下生活就是人生目的;叔本华认为,对抗生命意志是人生目的;尼采认为,努力当超人是人生目的;黑格尔更是认为整个历史都是有目的的,个人的人生目的是去努力实现历史的目的。可是,当形而上学不存在以后,这些目的就都不存在了。这世上没有终极真理,那就没有什么必须无条件为之奋斗的目的,那么,人到底为什么活着?人来到这个世上也不具备什么特别的目的。萨特就说,世间万物的存在都是偶然的。也就是说,稀里糊涂,没有任何理由,人类和世界就这么存在了。总而言之,人生也好,世界也好,没有任何目的。

按照佛教的观点,无明也是没有目的的。

问题是,人类能理解的故事有一定的固定模式。这个模式经过人类文明的千锤百炼之后,早就固定下来:故事必须有开头,有情节,有高潮,有结尾。任何一个能被大众接受的、听着比较“正常”的故事都得有这几个要素。试想,假如我们给别人讲一个没有开头的故事,我们说:“小王,我跟你说个事,那两个人后来成好朋友了……”小王会立刻打断我:“等等……你说什么,我没听懂。”小王为什么会拒斥这个故事呢?因为没有开头的故事对他来说,不可能提供有用的信息,他没有办法理解。再试想,假如一个故事没有高潮,或者没有结尾,那会怎么样呢?我们给别人讲一个故事,讲到最关键的时候突然停下来不讲了。那个人就会忍不住问:“继续讲啊,然后呢?”如果我们不讲,他甚至会生气:“你这个人怎么这样呢,说话说一半!”为什么他会生气?因为他的理性思维难以接受一个没有解决冲突和悬念的故事,甚至会因为过于难受而感到愤怒。为什么世界各地、各种年龄、各种文化背景的人都愿意去看好莱坞电影,看完之后都会心满意足?原因之一,是好莱坞电影的故事严格遵守开头、情节、高潮、结尾的故事模式,这样的模式符合人类对故事的预期,这个预期是全人类共有的。我们对整个世界的了解,都建立在一个有头有尾、有情节、有高潮的故事的基础上。

第二十章 人生的意义

该如何找到自己的人生意义?我认为最有效的办法,是逼迫自己直面死亡。我们问人生的意义是什么,其实就是在给自己的人生找一个目标,就是在问:“我为什么活着?”这也就等于在问:“我为什么不立刻自杀?”加缪说过:“真正严肃的哲学问题只有一个,那就是自杀。”维克多・弗兰克是一名奥地利犹太人。二战的时候他被关进纳粹集中营,经历了地狱般的磨难后侥幸逃生。战后他成了一名心理学家。他在治疗的时候常问病人:“你为什么不自杀?”因为借助病人的回答,他可以“为一个伤心的人编织出意义和责任”。假如你能顺利地回答“我为什么不自杀”的问题,如“我不想死,是因为我还想到处旅游,吃好吃的”“我不想死是因为我不能让父母伤心”。那么,这些答案就是你现在的人生意义。假如你的回答是“我不觉得活着有什么意义,我只是怕死”呢?那就请你想象一下死亡来临时的感觉吧。一个无神论者在面临死亡的巨大恐惧的时候,有时,求生的本能会让头脑拼命地给自己寻找活下去的理由。这个理由,也就是每个人的人生意义。有些和死神擦肩而过的人说,经过这一场磨难,自己大彻大悟,对人生有了更高层次的看法了。可是,我们每个人都知道自己早晚会死,为什么非要死到临头的时候才会大彻大悟呢?那就是因为绝大部分人平时从不愿意直面死亡,潜意识里认为自己可以永远逃避死亡。所以只有死到临头,才会开始反省人生。我们既然知道了这个道理,那就不妨早一点直面死亡,早一点把这件事想明白。怕死还意味着我们要珍惜生命。

作为一个佛教徒,我还比较好回答这个问题,我不自杀是因为自杀违反了佛教的纪律,佛教的根本五戒之一是不杀生,杀死自己也是杀生,所以不可以做。对于死亡,我是不怎么害怕的。从佛教的观点来看,出生和死亡只是相对意义上的事情,在绝对意义上,并没有出生和死亡这回事,我们的本性,或者说是佛性,或者说是世界的真相,或者说世界的本质,或者说是至高无上的真理,是超越时空,超越一切概念,也超越死亡的。所以在绝对真理面前,死亡只是一件微不足道的小事。即使在相对意义上讲,死亡也难以定义,我们每一个瞬间都在死亡,或者说是迈向死亡,到最后真正死亡的那一瞬间,我们应该是回归了绝对真理的怀抱。当然回归怀抱这些词都不准确,但是没有办法,我总要用人类的语言,除了人类的语言以外,作为人类,我没有别的语言可以用。所以归根到底,对于死亡我既不期待,也不讨厌,总有那么一天我会死去,但是我无所畏惧。在死亡之前,我希望能够通过修行在这无明造成的轮回中,偶尔能够瞥见绝对真理,相信如果能够在活着的时候能够瞥见甚至亲身体验绝对的真理,我一定能够对死亡后的情形更有信心。就目前而言,虽然我偶尔能够体会前所未有的平静,但是我不确定那是不是绝对真理,同时我觉得所有宗教里面描述出来的天堂地狱一定不是绝对真理,绝对真理一定是超越这些文字描述的,而且应该是超越物质的。虽然没有任何证据,但是我对这个超越概念,超越理性的绝对真理有无比的信心。正因为这个信心,我才能对死亡这么淡定。

点评

★★★★★
风趣幽默,通俗易懂。关于西方哲学,如果你只想看一本书,我觉得这本就够了。我以前也看过一些其他的西方哲学方面的书,印象都很模糊了,感觉里面的概念太多,读起来太烧脑。这一本就不一样了,完全是用逻辑和通俗的语言来讲哲学,感觉就像一个智者在陪你聊天一样。我看这本书也是因为这个作者,之前看了他写的另外两本书,一本是佛教说些什么,一本是讲中国史的,都写得很好,微信读书里面都有。强力推荐。

分类
编程

2019-12-15-学习c最少必要知识

学习c最少必要知识

C – Program Structure

A C program basically consists of the following parts −

  • Preprocessor Commands
  • Functions
  • Variables
  • Statements & Expressions
  • Comments

C – Basic Syntax

A C program consists of various tokens and a token is either a keyword, an identifier, a constant, a string literal, or a symbol.

In a C program, the semicolon is a statement terminator. That is, each individual statement must be ended with a semicolon. It indicates the end of one logical entity.

Comments are like helping text in your C program and they are ignored by the compiler. They start with /* and terminate with the characters */ or start with //.

A C identifier is a name used to identify a variable, function, or any other user-defined item. An identifier starts with a letter A to Z, a to z, or an underscore ‘_’ followed by zero or more letters, underscores, and digits (0 to 9). C is a case-sensitive programming language.

The following list shows the reserved words in C. These reserved words may not be used as constants or variables or any other identifier names.

auto else long switch break enum register typedef case extern return union char float short unsigned const for signed void continue goto sizeof volatile default if static while do int struct _Packed double

A line containing only whitespace, possibly with a comment, is known as a blank line, and a C compiler totally ignores it.

Whitespace is the term used in C to describe blanks, tabs, newline characters and comments. Whitespace separates one part of a statement from another and enables the compiler to identify where one element in a statement, such as int, ends and the next element begins.

C – Data Types

Data types in c refer to an extensive system used for declaring variables or functions of different types. The type of a variable determines how much space it occupies in storage and how the bit pattern stored is interpreted.

The types in C can be classified as follows:

  • Basic Types: They are arithmetic types and are further classified into: (a) integer types and (b) floating-point types.
  • Enumerated types: They are again arithmetic types and they are used to define variables that can only assign certain discrete integer values throughout the program.
  • The type void: The type specifier void indicates that no value is available.
  • Derived types: They include (a) Pointer types, (b) Array types, (c) Structure types, (d) Union types and (e) Function types.

Integer Types

TypeStorage sizeValue range
char1 byte-128 to 127 or 0 to 255
unsigned char1 byte0 to 255
signed char1 byte-128 to 127
int2 or 4 bytes-32,768 to 32,767 or -2,147,483,648 to 2,147,483,647
unsigned int2 or 4 bytes0 to 65,535 or 0 to 4,294,967,295
short2 bytes-32,768 to 32,767
unsigned short2 bytes0 to 65,535
long8 bytes-9223372036854775808 to 9223372036854775807
unsigned long8 bytes0 to 18446744073709551615

Floating-Point Types

TypeStorage sizeValue rangePrecision
float4 byte1.2E-38 to 3.4E+386 decimal places
double8 byte2.3E-308 to 1.7E+30815 decimal places
long double10 byte3.4E-4932 to 1.1E+493219 decimal places

The void Type

  • Function returns as void : There are various functions in C which do not return any value or you can say they return void. A function with no return value has the return type as void. For example, void exit (int status);
  • Function arguments as void: There are various functions in C which do not accept any parameter. A function with no parameter can accept a void. For example, int rand(void);
  • Pointers to void: A pointer of type void * represents the address of an object, but not its type. For example, a memory allocation function void *malloc( size_t size ); returns a pointer to void which can be casted to any data type.

C – Variables

A variable is nothing but a name given to a storage area that our programs can manipulate. Each variable in C has a specific type, which determines the size and layout of the variable’s memory; the range of values that can be stored within that memory; and the set of operations that can be applied to the variable.

There are the following basic variable types:

  • char: Typically a single octet(one byte). This is an integer type.
  • int: The most natural size of integer for the machine.
  • float: A single-precision floating point value.
  • double: A double-precision floating point value.
  • void: Represents the absence of type.

A variable definition tells the compiler where and how much storage to create for the variable. A variable definition specifies a data type and contains a list of one or more variables of that type as follows − type variable_list;

Here, type must be a valid C data type including char, w_char, int, float, double, bool, or any user-defined object; and variable_list may consist of one or more identifier names separated by commas. Some valid declarations are shown here −

int    i, j, k;
char   c, ch;
float  f, salary;
double d;

Variables can be initialized (assigned an initial value) in their declaration. The initializer consists of an equal sign followed by a constant expression as follows − type variable_name = value; Some examples are −

extern int d = 3, f = 5;    // declaration of d and f.
int d = 3, f = 5;           // definition and initializing d and f.
byte z = 22;                // definition and initializes z.
char x = 'x';               // the variable x has the value 'x'.

For definition without an initializer: variables with static storage duration are implicitly initialized with NULL (all bytes have the value 0); the initial value of all other variables are undefined.

A variable declaration provides assurance to the compiler that there exists a variable with the given type and name so that the compiler can proceed for further compilation without requiring the complete detail about the variable. A variable definition has its meaning at the time of compilation only, the compiler needs actual variable definition at the time of linking the program.

A variable declaration is useful when you are using multiple files and you define your variable in one of the files which will be available at the time of linking of the program. You will use the keyword extern to declare a variable at any place. Though you can declare a variable multiple times in your C program, it can be defined only once in a file, a function, or a block of code.

There are two kinds of expressions in C −

lvalue − Expressions that refer to a memory location are called “lvalue” expressions. An lvalue may appear as either the left-hand or right-hand side of an assignment.

rvalue − The term rvalue refers to a data value that is stored at some address in memory. An rvalue is an expression that cannot have a value assigned to it which means an rvalue may appear on the right-hand side but not on the left-hand side of an assignment.

Variables are lvalues and so they may appear on the left-hand side of an assignment. Numeric literals are rvalues and so they may not be assigned and cannot appear on the left-hand side. Take a look at the following valid and invalid statements −

int g = 20; // valid statement
10 = 20; // invalid statement; would generate compile-time error

C – Constants and Literals

Constants refer to fixed values that the program may not alter during its execution. These fixed values are also called literals.

Constants can be of any of the basic data types like an integer constant, a floating constant, a character constant, or a string literal. There are enumeration constants as well.

Constants are treated just like regular variables except that their values cannot be modified after their definition.

An integer literal can be a decimal, octal, or hexadecimal constant. A prefix specifies the base or radix: 0x or 0X for hexadecimal, 0 for octal, and nothing for decimal.

An integer literal can also have a suffix that is a combination of U and L, for unsigned and long, respectively. The suffix can be uppercase or lowercase and can be in any order.

Here are some examples of integer literals −

212         /* Legal */
215u        /* Legal */
0xFeeL      /* Legal */
078         /* Illegal: 8 is not an octal digit */
032UU       /* Illegal: cannot repeat a suffix */
85         /* decimal */
0213       /* octal */
0x4b       /* hexadecimal */
30         /* int */
30u        /* unsigned int */
30l        /* long */
30ul       /* unsigned long */

A floating-point literal has an integer part, a decimal point, a fractional part, and an exponent part. You can represent floating point literals either in decimal form or exponential form.

While representing decimal form, you must include the decimal point, the exponent, or both; and while representing exponential form, you must include the integer part, the fractional part, or both. The signed exponent is introduced by e or E.

Here are some examples of floating-point literals −

3.14159       /* Legal */
314159E-5L    /* Legal */
510E          /* Illegal: incomplete exponent */
210f          /* Illegal: no decimal or exponent */
.e55          /* Illegal: missing integer or fraction */

Character literals are enclosed in single quotes, e.g., ‘x’ can be stored in a simple variable of char type.

A character literal can be a plain character (e.g., ‘x’), an escape sequence (e.g., ‘\t’), or a universal character (e.g., ‘\u02C0’).

There are certain characters in C that represent special meaning when preceded by a backslash for example, newline (\n) or tab (\t).

Escape sequenceMeaning
\|\ character
\’‘ character
\”” character
\?? character
\aAlert or bell
\bBackspace
\fForm feed
\nNewline
\rCarriage return
\tHorizontal tab
\vVertical tab
\oooOctal number of one to three digits
\xhh . . .Hexadecimal number of one or more digits

String literals or constants are enclosed in double quotes “”. A string contains characters that are similar to character literals: plain characters, escape sequences, and universal characters.

You can break a long line into multiple lines using string literals and separating them using white spaces.

There are two simple ways in C to define constants −

  • Using #define preprocessor.
  • Using const keyword.

Given below is the form to use #define preprocessor to define a constant − #define identifier value

You can use const prefix to declare constants with a specific type as follows −const type variable = value;

Note that it is a good programming practice to define constants in CAPITALS.

C – Storage Classes

A storage class defines the scope (visibility) and life-time of variables and/or functions within a C Program. They precede the type that they modify. We have four different storage classes in a C program −

  • auto
  • register
  • static
  • extern

The auto storage class is the default storage class for all local variables.

The register storage class is used to define local variables that should be stored in a register instead of RAM. This means that the variable has a maximum size equal to the register size (usually one word) and can’t have the unary ‘&’ operator applied to it (as it does not have a memory location).

The register should only be used for variables that require quick access such as counters. It should also be noted that defining ‘register’ does not mean that the variable will be stored in a register. It means that it MIGHT be stored in a register depending on hardware and implementation restrictions.

The static storage class instructs the compiler to keep a local variable in existence during the life-time of the program instead of creating and destroying it each time it comes into and goes out of scope. Therefore, making local variables static allows them to maintain their values between function calls.

The static modifier may also be applied to global variables. When this is done, it causes that variable’s scope to be restricted to the file in which it is declared.

In C programming, when static is used on a global variable, it causes only one copy of that member to be shared by all the objects of its class.

The extern storage class is used to give a reference of a global variable that is visible to ALL the program files. When you use ‘extern’, the variable cannot be initialized however, it points the variable name at a storage location that has been previously defined.

When you have multiple files and you define a global variable or function, which will also be used in other files, then extern will be used in another file to provide the reference of defined variable or function. Just for understanding, extern is used to declare a global variable or function in another file.

The extern modifier is most commonly used when there are two or more files sharing the same global variables or functions as explained below.

First File: main.c

#include <stdio.h>
int count ;
extern void write_extern();
main() {
   count = 5;
   write_extern();
}

Second File: support.c

#include <stdio.h>
extern int count;
void write_extern(void) {
   printf("count is %d\n", count);
}

Here, extern is being used to declare count in the second file, where as it has its definition in the first file, main.c. Now, compile these two files as follows − $gcc main.c support.c

It will produce the executable program a.out. When this program is executed, it produces the following result − count is 5

C – Operators

An operator is a symbol that tells the compiler to perform specific mathematical or logical functions. C language is rich in built-in operators and provides the following types of operators −

  • Arithmetic Operators
  • Relational Operators
  • Logical Operators
  • Bitwise Operators
  • Assignment Operators
  • Misc Operators

Arithmetic Operators

Assume variable A holds 10 and variable B holds 20 then −

OperatorDescriptionExample
+Adds two operands.A + B = 30
Subtracts second operand from the first.A − B = -10
*Multiplies both operands.A * B = 200
/Divides numerator by de-numerator.B / A = 2
%Modulus Operator and remainder of after an integer division.B % A = 0
++Increment operator increases the integer value by one.A++ = 11
Decrement operator decreases the integer value by one.A– = 9

Relational Operators

Assume variable A holds 10 and variable B holds 20 then −

OperatorDescriptionExample
==Checks if the values of two operands are equal or not. If yes, then the condition becomes true.(A == B) is not true.
!=Checks if the values of two operands are equal or not. If the values are not equal, then the condition becomes true.(A != B) is true.
>Checks if the value of left operand is greater than the value of right operand. If yes, then the condition becomes true.(A > B) is not true.
<Checks if the value of left operand is less than the value of right operand. If yes, then the condition becomes true.(A < B) is true.
>=Checks if the value of left operand is greater than or equal to the value of right operand. If yes, then the condition becomes true.(A >= B) is not true.
<=Checks if the value of left operand is less than or equal to the value of right operand. If yes, then the condition becomes true.(A <= B) is true.

Logical Operators

Assume variable A holds 1 and variable B holds 0, then −

OperatorDescriptionExample
&&Called Logical AND operator. If both the operands are non-zero, then the condition becomes true.(A && B) is false.
||Called Logical OR Operator. If any of the two operands is non-zero, then the condition becomes true.(A || B) is true.
!Called Logical NOT Operator. It is used to reverse the logical state of its operand. If a condition is true, then Logical NOT operator will make it false.!(A && B) is true.

Bitwise Operators

pqp & qp | qp ^ q
00000
01011
11110
10011

Assume A = 60 and B = 13 in binary format, they will be as follows −

A = 0011 1100
B = 0000 1101
A&B = 0000 1100
A|B = 0011 1101
A^B = 0011 0001
~A = 1100 0011

The following table lists the bitwise operators supported by C. Assume variable ‘A’ holds 60 and variable ‘B’ holds 13, then −

OperatorDescriptionExample
&Binary AND Operator copies a bit to the result if it exists in both operands.(A & B) = 12, i.e., 0000 1100
|Binary OR Operator copies a bit if it exists in either operand.(A | B) = 61, i.e., 0011 1101
^Binary XOR Operator copies the bit if it is set in one operand but not both.(A ^ B) = 49, i.e., 0011 0001
~Binary One’s Complement Operator is unary and has the effect of ‘flipping’ bits.(~A ) = ~(60), i.e,. -0111101
<<Binary Left Shift Operator. The left operands value is moved left by the number of bits specified by the right operand.A << 2 = 240 i.e., 1111 0000
>>Binary Right Shift Operator. The left operands value is moved right by the number of bits specified by the right operand.A >> 2 = 15 i.e., 0000 1111

Assignment Operators

OperatorDescriptionExample
=Simple assignment operator. Assigns values from right side operands to left side operandC = A + B will assign the value of A + B to C
+=Add AND assignment operator. It adds the right operand to the left operand and assign the result to the left operand.C += A is equivalent to C = C + A
-=Subtract AND assignment operator. It subtracts the right operand from the left operand and assigns the result to the left operand.C -= A is equivalent to C = C – A
*=Multiply AND assignment operator. It multiplies the right operand with the left operand and assigns the result to the left operand.C *= A is equivalent to C = C * A
/=Divide AND assignment operator. It divides the left operand with the right operand and assigns the result to the left operand.C /= A is equivalent to C = C / A
%=Modulus AND assignment operator. It takes modulus using two operands and assigns the result to the left operand.C %= A is equivalent to C = C % A
<<=Left shift AND assignment operator.C <<= 2 is same as C = C << 2
>>=Right shift AND assignment operator.C >>= 2 is same as C = C >> 2
&=Bitwise AND assignment operator.C &= 2 is same as C = C & 2
^=Bitwise exclusive OR and assignment operator.C ^= 2 is same as C = C ^ 2
|=Bitwise inclusive OR and assignment operator.C |= 2 is same as C = C | 2

Operators Precedence in C

Operator precedence determines the grouping of terms in an expression and decides how an expression is evaluated. Certain operators have higher precedence than others; for example, the multiplication operator has a higher precedence than the addition operator.

Here, operators with the highest precedence appear at the top of the table, those with the lowest appear at the bottom. Within an expression, higher precedence operators will be evaluated first.

CategoryOperatorAssociativity
Postfix() [] -> . ++ —Left to right
Unary+ – ! ~ ++ — (type)* & sizeofRight to left
Multiplicative* / %Left to right
Additive+ –Left to right
Shift<< >>Left to right
Relational< <= > >=Left to right
Equality== !=Left to right
Bitwise AND&Left to right
Bitwise XOR^Left to right
Bitwise OR|Left to right
Logical AND&&Left to right
Logical OR||Left to right
Conditional?:Right to left
Assignment= += -= *= /= %=>>= <<= &= ^= |=Right to left
Comma,Left to right

C – Decision Making

Decision making structures require that the programmer specifies one or more conditions to be evaluated or tested by the program, along with a statement or statements to be executed if the condition is determined to be true, and optionally, other statements to be executed if the condition is determined to be false.

C programming language assumes any non-zero and non-null values as true, and if it is either zero or null, then it is assumed as false value.

C programming language provides the following types of decision making statements.

Sr.No.Statement & Description
1if statement
An if statement consists of a boolean expression followed by one or more statements.
2if…else statement
An if statement can be followed by an optional else statement, which executes when the Boolean expression is false.
3nested if statements
You can use one if or else if statement inside another if or else if statement(s).
4switch statement
A switch statement allows a variable to be tested for equality against a list of values.
5nested switch statements
You can use one switch statement inside another switch statement(s).

We have covered conditional operator ? : in the previous chapter which can be used to replace if…else statements. It has the following general form −

Exp1 ? Exp2 : Exp3;

Where Exp1, Exp2, and Exp3 are expressions. Notice the use and placement of the colon.

The value of a ? expression is determined like this −

  • Exp1 is evaluated. If it is true, then Exp2 is evaluated and becomes the value of the entire ? expression.
  • If Exp1 is false, then Exp3 is evaluated and its value becomes the value of the expression.

C – Loops

A loop statement allows us to execute a statement or group of statements multiple times.

C programming language provides the following types of loops to handle looping requirements.

Sr.No.Loop Type & Description
1while loop
Repeats a statement or group of statements while a given condition is true. It tests the condition before executing the loop body.
2for loop
Executes a sequence of statements multiple times and abbreviates the code that manages the loop variable.
3do…while loop
It is more like a while statement, except that it tests the condition at the end of the loop body.
4nested loops
You can use one or more loops inside any other while, for, or do..while loop.

Loop control statements change execution from its normal sequence. When execution leaves a scope, all automatic objects that were created in that scope are destroyed.

C supports the following control statements.

Sr.No.Control Statement & Description
1break statement
Terminates the loop or switch statement and transfers execution to the statement immediately following the loop or switch.
2continue statement
Causes the loop to skip the remainder of its body and immediately retest its condition prior to reiterating.
3goto statement
Transfers control to the labeled statement.

A loop becomes an infinite loop if a condition never becomes false. The for loop is traditionally used for this purpose. Since none of the three expressions that form the ‘for’ loop are required, you can make an endless loop by leaving the conditional expression empty.

C – Functions

A function is a group of statements that together perform a task. Every C program has at least one function, which is main(), and all the most trivial programs can define additional functions.

A function declaration tells the compiler about a function’s name, return type, and parameters. A function definition provides the actual body of the function.

The C standard library provides numerous built-in functions that your program can call. For example, strcat() to concatenate two strings, memcpy() to copy one memory location to another location, and many more functions.

A function can also be referred as a method or a sub-routine or a procedure, etc.

The general form of a function definition in C programming language is as follows −

return_type function_name( parameter list ) {
   body of the function
}

A function definition in C programming consists of a function header and a function body. Here are all the parts of a function −

  • Return Type − A function may return a value. The return_type is the data type of the value the function returns. Some functions perform the desired operations without returning a value. In this case, the return_type is the keyword void.
  • Function Name − This is the actual name of the function. The function name and the parameter list together constitute the function signature.
  • Parameters − A parameter is like a placeholder. When a function is invoked, you pass a value to the parameter. This value is referred to as actual parameter or argument. The parameter list refers to the type, order, and number of the parameters of a function. Parameters are optional; that is, a function may contain no parameters.
  • Function Body − The function body contains a collection of statements that define what the function does.

A function declaration tells the compiler about a function name and how to call the function. The actual body of the function can be defined separately.

A function declaration has the following parts − return_type function_name( parameter list );

Parameter names are not important in function declaration only their type is required.

While creating a C function, you give a definition of what the function has to do. To use a function, you will have to call that function to perform the defined task.

When a program calls a function, the program control is transferred to the called function. A called function performs a defined task and when its return statement is executed or when its function-ending closing brace is reached, it returns the program control back to the main program.

To call a function, you simply need to pass the required parameters along with the function name, and if the function returns a value, then you can store the returned value.

Like:

#include <stdio.h>
int max(int a, int b) { return (a > b) ? a : b; }
int main() {
    int a = 100, b = 200;
    printf("Max value is : %d\n", max(a, b));
    return 0;
}

If a function is to use arguments, it must declare variables that accept the values of the arguments. These variables are called the formal parameters of the function.

Formal parameters behave like other local variables inside the function and are created upon entry into the function and destroyed upon exit.

While calling a function, there are two ways in which arguments can be passed to a function −

  • Call by value: This method copies the actual value of an argument into the formal parameter of the function. In this case, changes made to the parameter inside the function have no effect on the argument.
  • Call by reference: This method copies the address of an argument into the formal parameter. Inside the function, the address is used to access the actual argument used in the call. This means that changes made to the parameter affect the argument.

By default, C uses call by value to pass arguments. In general, it means the code within a function cannot alter the arguments used to call the function.

C – Scope Rules

A scope in any programming is a region of the program where a defined variable can have its existence and beyond that variable it cannot be accessed. There are three places where variables can be declared in C programming language −

  • Inside a function or a block which is called local variables.
  • Outside of all functions which is called global variables.
  • In the definition of function parameters which are called formal parameters.

Variables that are declared inside a function or block are called local variables. They can be used only by statements that are inside that function or block of code. Local variables are not known to functions outside their own.

Global variables are defined outside a function, usually on top of the program. Global variables hold their values throughout the lifetime of your program and they can be accessed inside any of the functions defined for the program. A global variable can be accessed by any function. That is, a global variable is available for use throughout your entire program after its declaration.

A program can have same name for local and global variables but the value of local variable inside a function will take preference.

Formal parameters, are treated as local variables with-in a function and they take precedence over global variables.

When a local variable is defined, it is not initialized by the system, you must initialize it yourself. Global variables are initialized automatically by the system when you define them as follows −

Data TypeInitial Default Value
int0
char‘\0’
float0
double0
pointerNULL

C – Arrays

Arrays a kind of data structure that can store a fixed-size sequential collection of elements of the same type. An array is used to store a collection of data, but it is often more useful to think of an array as a collection of variables of the same type.

Instead of declaring individual variables, such as number0, number1, …, and number99, you declare one array variable such as numbers and use numbers[0], numbers[1], and …, numbers[99] to represent individual variables. A specific element in an array is accessed by an index.

All arrays consist of contiguous memory locations. The lowest address corresponds to the first element and the highest address to the last element.

To declare an array in C, a programmer specifies the type of the elements and the number of elements required by an array as follows −
type arrayName [ arraySize ];

This is called a single-dimensional array. The arraySize must be an integer constant greater than zero and type can be any valid C data type, such as – double balance[10];

You can initialize an array in C either one by one or using a single statement as follows −
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};

The number of values between braces { } cannot be larger than the number of elements that we declare for the array between square brackets [ ].

If you omit the size of the array, an array just big enough to hold the initialization is created. Therefore, if you write −
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};

You will create exactly the same array as you did in the previous example.

The above statement assigns the 5th element in the array with a value of 50.0. All arrays have 0 as the index of their first element which is also called the base index and the last index of an array will be total size of the array minus 1.

An element is accessed by indexing the array name. This is done by placing the index of the element within square brackets after the name of the array. For example −
double salary = balance[9];

The above statement will take the 10th element from the array and assign the value to salary variable.

The following important concepts related to array should be clear to a C programmer −

  1. Multi-dimensional arrays: C supports multidimensional arrays. The simplest form of the multidimensional array is the two-dimensional array.
  2. Passing arrays to functions: You can pass to the function a pointer to an array by specifying the array’s name without an index.
  3. Return array from a function: C allows a function to return an array.
  4. Pointer to an array: You can generate a pointer to the first element of an array by simply specifying the array name, without any index.

C – Pointers

A pointer is a variable whose value is the address of another variable, i.e., direct address of the memory location. Like any variable or constant, you must declare a pointer before using it to store any variable address. The general form of a pointer variable declaration is −
type *var-name;

Here, type is the pointer’s base type; it must be a valid C data type and var-name is the name of the pointer variable. The asterisk * used to declare a pointer is the same asterisk used for multiplication. However, in this statement the asterisk is being used to designate a variable as a pointer. Take a look at some of the valid pointer declarations −

int    *ip;    /* pointer to an integer */
double *dp;    /* pointer to a double */
float  *fp;    /* pointer to a float */
char   *ch     /* pointer to a character */

The actual data type of the value of all pointers, whether integer, float, character, or otherwise, is the same, a long hexadecimal number that represents a memory address. The only difference between pointers of different data types is the data type of the variable or constant that the pointer points to.

There are a few important operations, which we will do with the help of pointers very frequently. (a) We define a pointer variable, (b) assign the address of a variable to a pointer and (c) finally access the value at the address available in the pointer variable. This is done by using unary operator * that returns the value of the variable located at the address specified by its operand.

#include <stdio.h>
int main() {
    int var = 20; /* actual variable declaration */
    int *ip;      /* pointer variable declaration */
    ip = &var; /* store address of var in pointer variable*/
    printf("Address of var variable: %p\n", &var);
    /* address stored in pointer variable */
    printf("Address stored in ip variable: %p\n", ip);
    /* access the value using the pointer */
    printf("Value of *ip variable: %d\n", *ip);
    return 0;
}

It is always a good practice to assign a NULL value to a pointer variable in case you do not have an exact address to be assigned. This is done at the time of variable declaration. A pointer that is assigned NULL is called a null pointer.

The NULL pointer is a constant with a value of zero defined in several standard libraries.

Pointers have many but easy concepts and they are very important to C programming. The following important pointer concepts should be clear to any C programmer −

  1. Pointer arithmetic: There are four arithmetic operators that can be used in pointers: ++, –, +, –
  2. Array of pointers: You can define arrays to hold a number of pointers.
  3. Pointer to pointer: C allows you to have pointer on a pointer and so on.
  4. Passing pointers to functions in C: Passing an argument by reference or by address enable the passed argument to be changed in the calling function by the called function.
  5. Return pointer from functions in C: C allows a function to return a pointer to the local variable, static variable, and dynamically allocated memory as well.

C – Strings

Strings are actually one-dimensional array of characters terminated by a null character ‘\0’. Thus a null-terminated string contains the characters that comprise the string followed by a null.

The following declaration and initialization create a string consisting of the word “Hello”. To hold the null character at the end of the array, the size of the character array containing the string is one more than the number of characters in the word “Hello.”

char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

Notice that char s[100]="hello"; printf("%d", strlen(s); will output 5 instead of 100;

If you follow the rule of array initialization then you can write the above statement as follows −
char greeting[] = "Hello";

The C compiler automatically places the ‘\0’ at the end of the string when it initializes the array.

C supports a wide range of functions that manipulate null-terminated strings −

  1. strcpy(s1, s2); Copies string s2 into string s1.
  2. strcat(s1, s2); Concatenates string s2 onto the end of string s1.
  3. strlen(s1); Returns the length of string s1.
  4. strcmp(s1, s2); Returns 0 if s1 and s2 are the same; less than 0 if s1s2.
  5. strchr(s1, ch); Returns a pointer to the first occurrence of character ch in string s1.
  6. strstr(s1, s2); Returns a pointer to the first occurrence of string s2 in string s1.

C – Structures

Arrays allow to define type of variables that can hold several data items of the same kind. Similarly structure is another user defined data type available in C that allows to combine data items of different kinds. Structures are used to represent a record.

To define a structure, you must use the struct statement. The struct statement defines a new data type, with more than one member. The format of the struct statement is as follows −

struct [structure tag] {
   member definition;
   member definition;
   ...
   member definition;
} [one or more structure variables];

struct without a tag has an “anonymous structure type” and you won’t be able to declare another variable of the same type.

To access any member of a structure, we use the member access operator (.). The member access operator is coded as a period between the structure variable name and the structure member that we wish to access. You would use the keyword struct to define variables of structure type.

#include <stdio.h>
#include <string.h>
struct Books {
    char title[50];
    char author[50];
    char subject[100];
    int book_id;
};
int main() {
    struct Books book; /* Declare book of type Book */
    /* book 1 specification */
    // book.title = "C Programming"; // will cause error: array type 'char [50]' is not assignable
    strcpy(book.title, "C Programming");
    strcpy(book.author, "Nuha Ali");
    strcpy(book.subject, "C Programming Tutorial");
    book.book_id = 6495407;
    /* print book info */
    printf("Book title : %s\n", book.title);
    printf("Book author : %s\n", book.author);
    printf("Book subject : %s\n", book.subject);
    printf("Book book_id : %d\n", book.book_id);
    return 0;
}

You can pass a structure as a function argument in the same way as you pass any other variable or pointer.

You can define pointers to structures in the same way as you define pointer to any other variable −
struct Books *pb = &book;

To find the address of a structure variable, place the ‘&’; operator before the structure’s name.

To access the members of a structure using a pointer to that structure, you must use the → operator as follows − pb->id

#include <stdio.h>
#include <string.h>
struct Books {
    char title[50];
    char author[50];
    char subject[100];
    int book_id;
};
void printBook(struct Books);
void updateBookId(struct Books *, int);
int main() {
    struct Books book, *pb = &book;
    strcpy(book.title, "C Programming");
    strcpy(book.author, "Nuha Ali");
    strcpy(book.subject, "C Programming Tutorial");
    book.book_id = 6495407;
    printBook(book);
    updateBookId(pb, 100);
    strcpy(pb->author, "Alan");
    printBook(book);
    return 0;
}
void printBook(struct Books book) {
    printf("Book title : %s\n", book.title);
    printf("Book author : %s\n", book.author);
    printf("Book subject : %s\n", book.subject);
    printf("Book book_id : %d\n", book.book_id);
}
void updateBookId(struct Books *book, int id) { book->book_id = id; }

Bit Fields

Bit Fields allow the packing of data in a structure. This is especially useful when memory or data storage is at a premium. Typical examples include −

Packing several objects into a machine word. e.g. 1 bit flags can be compacted.

Reading external file formats — non-standard file formats could be read in, e.g., 9-bit integers.

C allows us to do this in a structure definition by putting :bit length after the variable. For example −

struct packed_struct {
   unsigned int f1:1;
   unsigned int f2:1;
   unsigned int f3:1;
   unsigned int f4:1;
   unsigned int type:4;
   unsigned int my_int:9;
} pack;

Here, the packed_struct contains 6 members: Four 1 bit flags f1..f3, a 4-bit type and a 9-bit my_int.

C automatically packs the above bit fields as compactly as possible, provided that the maximum length of the field is less than or equal to the integer word length of the computer. If this is not the case, then some compilers may allow memory overlap for the fields while others would store the next field in the next word.

C – Unions

A union is a special data type available in C that allows to store different data types in the same memory location. You can define a union with many members, but only one member can contain a value at any given time. Unions provide an efficient way of using the same memory location for multiple-purpose.

To define a union, you must use the union statement in the same way as you did while defining a structure. The union statement defines a new data type with more than one member for your program. The format of the union statement is as follows −

union [union tag] {
   member definition;
   member definition;
   ...
   member definition;
} [one or more union variables];

The union tag is optional and each member definition is a normal variable definition, such as int i; or float f; or any other valid variable definition. At the end of the union’s definition, before the final semicolon, you can specify one or more union variables but it is optional. Here is the way you would define a union type named Data having three members i, f, and str −

union Data {
   int i;
   float f;
   char str[20];
} data;

Now, a variable of Data type can store an integer, a floating-point number, or a string of characters. It means a single variable, i.e., same memory location, can be used to store multiple types of data. You can use any built-in or user defined data types inside a union based on your requirement.

The memory occupied by a union will be large enough to hold the largest member of the union. For example, in the above example, Data type will occupy 20 bytes of memory space because this is the maximum space which can be occupied by a character string. The following example displays the total memory size occupied by the above union −

#include <stdio.h>
#include <string.h>
union Data {
    int i;
    float f;
    char str[20];
};
int main() {
    union Data data;
    printf("Memory size occupied by data : %lu\n", sizeof(data)); //20
    return 0;
}

To access any member of a union, we use the member access operator (.). The member access operator is coded as a period between the union variable name and the union member that we wish to access. You would use the keyword union to define variables of union type. The following example shows how to use unions in a program −

#include <stdio.h>
#include <string.h>
union Data {
    int i;
    float f;
    char str[20];
};
int main() {
    union Data data;
    data.i = 10;
    data.f = 220.5;
    strcpy(data.str, "C Programming");
    printf("data.i : %d\n", data.i);
    printf("data.f : %f\n", data.f);
    printf("data.str : %s\n", data.str);
    return 0;
}

output:

data.i : 1917853763
data.f : 4122360580327794860452759994368.000000
data.str : C Programming

Here, we can see that the values of i and f members of union got corrupted because the final value assigned to the variable has occupied the memory location and this is the reason that the value of str member is getting printed very well.

Now let’s look into the same example once again where we will use one variable at a time which is the main purpose of having unions −

#include <stdio.h>
#include <string.h>
union Data {
    int i;
    float f;
    char str[20];
};
int main() {
    union Data data;
    data.i = 10;
    printf("data.i : %d\n", data.i);
    data.f = 220.5;
    printf("data.f : %f\n", data.f);
    strcpy(data.str, "C Programming");
    printf("data.str : %s\n", data.str);
    return 0;
}

output:

data.i : 10
data.f : 220.500000
data.str : C Programming

C – Bit Fields

struct {
   unsigned int widthValidated;
   unsigned int heightValidated;
} status;

can be rewritten as:

struct {
   unsigned int widthValidated : 1;
   unsigned int heightValidated : 1;
} status;

The above structure requires 4 bytes of memory space for status variable, but only 2 bits will be used to store the values.

If you will use up to 32 variables each one with a width of 1 bit, then also the status structure will use 4 bytes. However as soon as you have 33 variables, it will allocate the next slot of the memory and it will start using 8 bytes. Let us check the following example to understand the concept −

The declaration of a bit-field has the following form inside a structure −

struct {
   type [member_name] : width ;
};

type: An integer type that determines how a bit-field’s value is interpreted. The type may be int, signed int, or unsigned int.

member_name: The name of the bit-field.

width: The number of bits in the bit-field. The width must be less than or equal to the bit width of the specified type.

The variables defined with a predefined width are called bit fields. A bit field can hold more than a single bit; for example, if you need a variable to store a value from 0 to 7, then you can define a bit field with a width of 3 bits as follows −

struct {
   unsigned int age : 3;
} Age;

The above structure definition instructs the C compiler that the age variable is going to use only 3 bits to store the value. If you try to use more than 3 bits, then it will not allow you to do so. Let us try the following example −

#include <stdio.h>
#include <string.h>
struct {
    unsigned int age : 3;
} Age;
int main() {
    Age.age = 4;
    printf("Sizeof( Age ) : %lu\n", sizeof(Age));
    printf("Age.age : %d\n", Age.age);
    Age.age = 7;
    printf("Age.age : %d\n", Age.age);
    Age.age = 8;
    printf("Age.age : %d\n", Age.age); // output 0 with warning
    return 0;
}

C – typedef

The C programming language provides a keyword called typedef, which you can use to give a type a new name. Following is an example to define a term BYTE for one-byte numbers −

typedef unsigned char BYTE;

After this type definition, the identifier BYTE can be used as an abbreviation for the type unsigned char, for example..

BYTE b1, b2;

By convention, uppercase letters are used for these definitions to remind the user that the type name is really a symbolic abbreviation, but you can use lowercase, as follows −

typedef unsigned char byte;

You can use typedef to give a name to your user defined data types as well. For example,

typedef struct Books {
   char title[50];
   char author[50];
   char subject[100];
   int book_id;
} Book;

define is a C-directive which is also used to define the aliases for various data types similar to typedef but with the following differences −

typedef is limited to giving symbolic names to types only where as #define can be used to define alias for values as well, you can define 1 as ONE etc.

typedef interpretation is performed by the compiler whereas #define statements are processed by the pre-processor.

C – Input and Output

When we say Input, it means to feed some data into a program. An input can be given in the form of a file or from the command line. C programming provides a set of built-in functions to read the given input and feed it to the program as per requirement.

When we say Output, it means to display some data on screen, printer, or in any file. C programming provides a set of built-in functions to output the data on the computer screen as well as to save it in text or binary files.

C programming treats all the devices as files. So devices such as the display are addressed in the same way as files and the following three files are automatically opened when a program executes to provide access to the keyboard and screen.

Standard FileFile PointerDevice
Standard inputstdinKeyboard
Standard outputstdoutScreen
Standard errorstderrYour screen

The file pointers are the means to access the file for reading and writing purpose. This section explains how to read values from the screen and how to print the result on the screen.

The int getchar(void) function reads the next available character from the screen and returns it as an integer. This function reads only single character at a time. You can use this method in the loop in case you want to read more than one character from the screen.

The int putchar(int c) function puts the passed character on the screen and returns the same character. This function puts only single character at a time. You can use this method in the loop in case you want to display more than one character on the screen. Check the following example −

#include <stdio.h>
int main( ) {
   int c;
   printf( "Enter a value :");
   c = getchar( );
   printf( "\nYou entered: ");
   putchar( c );
   return 0;
}

The char *gets(char *s) function reads a line from stdin into the buffer pointed to by s until either a terminating newline or EOF (End of File).

The int puts(const char *s) function writes the string ‘s’ and ‘a’ trailing newline to stdout.

NOTE: Though it has been deprecated to use gets() function, Instead of using gets, you want to use fgets().

#include <stdio.h>
int main() {
    char str[100];
    printf("Enter a value :");
    // gets(str); warning: this program uses gets(), which is unsafe.
    fgets(str, 100, stdin);
    printf("You entered: ");
    puts(str);
    return 0;
}

The int scanf(const char *format, …) function reads the input from the standard input stream stdin and scans that input according to the format provided.

The int printf(const char *format, …) function writes the output to the standard output stream stdout and produces the output according to the format provided.

The format can be a simple constant string, but you can specify %s, %d, %c, %f, etc., to print or read strings, integer, character or float respectively. There are many other formatting options available which can be used based on requirements. Let us now proceed with a simple example to understand the concepts better −

Here, it should be noted that scanf() expects input in the same format as you provided %s and %d, which means you have to provide valid inputs like “string integer”. If you provide “string string” or “integer integer”, then it will be assumed as wrong input. Secondly, while reading a string, scanf() stops reading as soon as it encounters a space, so “this is test” are three strings for scanf().

C – File I/O

A file represents a sequence of bytes, regardless of it being a text file or a binary file. C programming language provides access on high level functions as well as low level (OS level) calls to handle file on your storage devices.

You can use the fopen( ) function to create a new file or to open an existing file. This call will initialize an object of the type FILE, which contains all the information necessary to control the stream. The prototype of this function call is as follows −

FILE *fopen( const char * filename, const char * mode );

Here, filename is a string literal, which you will use to name your file, and access mode can have one of the following values −

  1. r: Opens an existing text file for reading purpose.
  2. w: Opens a text file for writing. If it does not exist, then a new file is created. Here your program will start writing content from the beginning of the file.
  3. a: Opens a text file for writing in appending mode. If it does not exist, then a new file is created. Here your program will start appending content in the existing file content.
  4. r+: Opens a text file for both reading and writing.
  5. w+: Opens a text file for both reading and writing. It first truncates the file to zero length if it exists, otherwise creates a file if it does not exist.
  6. a+: Opens a text file for both reading and writing. It creates the file if it does not exist. The reading will start from the beginning but writing can only be appended.

If you are going to handle binary files, then you will use following access modes instead of the above mentioned ones −

"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"

To close a file, use the fclose( ) function. The prototype of this function is −

int fclose( FILE *fp );

The fclose(-) function returns zero on success, or EOF if there is an error in closing the file. This function actually flushes any data still pending in the buffer to the file, closes the file, and releases any memory used for the file. The EOF is a constant defined in the header file stdio.h.

Following is the simplest function to write individual characters to a stream −

int fputc( int c, FILE *fp );

The function fputc() writes the character value of the argument c to the output stream referenced by fp. It returns the written character written on success otherwise EOF if there is an error. You can use the following functions to write a null-terminated string to a stream −

int fputs( const char *s, FILE *fp );

The function fputs() writes the string s to the output stream referenced by fp. It returns a non-negative value on success, otherwise EOF is returned in case of any error. You can use int fprintf(FILE *fp,const char *format, …) function as well to write a string into a file.

#include <stdio.h>
int main() {
   FILE *fp;
   fp = fopen("test.log", "w+");
   fprintf(fp, "This is testing for fprintf...\n");
   fputs("This is testing for fputs...\n", fp);
   fclose(fp);
}

Given below is the simplest function to read a single character from a file −

int fgetc( FILE * fp );

The fgetc() function reads a character from the input file referenced by fp. The return value is the character read, or in case of any error, it returns EOF. The following function allows to read a string from a stream −

char *fgets( char *buf, int n, FILE *fp );

The functions fgets() reads up to n-1 characters from the input stream referenced by fp. It copies the read string into the buffer buf, appending a null character to terminate the string.

If this function encounters a newline character ‘\n’ or the end of the file EOF before they have read the maximum number of characters, then it returns only the characters read up to that point including the new line character. You can also use int fscanf(FILE *fp, const char *format, ...) function to read strings from a file, but it stops reading after encountering the first space character.

There are two functions, that can be used for binary input and output −

size_t fread(void *ptr, size_t size_of_elements, size_t number_of_elements, FILE *a_file);

size_t fwrite(const void *ptr, size_t size_of_elements, size_t number_of_elements, FILE *a_file);

Both of these functions should be used to read or write blocks of memories – usually arrays or structures.

C – Preprocessors

The C Preprocessor is not a part of the compiler, but is a separate step in the compilation process. In simple terms, a C Preprocessor is just a text substitution tool and it instructs the compiler to do required pre-processing before the actual compilation. We’ll refer to the C Preprocessor as CPP.

All preprocessor commands begin with a hash symbol (#). It must be the first nonblank character, and for readability, a preprocessor directive should begin in the first column. The following section lists down all the important preprocessor directives −

  1. #define: Substitutes a preprocessor macro.
  2. #include: Inserts a particular header from another file.
  3. #undef: Undefines a preprocessor macro.
  4. #ifdef: Returns true if this macro is defined.
  5. #ifndef: Returns true if this macro is not defined.
  6. #if: Tests if a compile time condition is true.
  7. #else: The alternative for #if.
  8. #elif: #else and #if in one statement.
  9. #endif: Ends preprocessor conditional.
  10. #error: Prints error message on stderr.
  11. #pragma: Issues special commands to the compiler, using a standardized method.

Examples:

#define MAX_ARRAY_LENGTH 20

This directive tells the CPP to replace instances of MAX_ARRAY_LENGTH with 20. Use #define for constants to increase readability.

#include <stdio.h>
#include "myheader.h"

These directives tell the CPP to get stdio.h from System Libraries and add the text to the current source file. The next line tells CPP to get myheader.h from the local directory and add the content to the current source file.

#undef  FILE_SIZE
#define FILE_SIZE 42

It tells the CPP to undefine existing FILE_SIZE and define it as 42.

#ifndef MESSAGE
   #define MESSAGE "You wish!"
#endif

It tells the CPP to define MESSAGE only if MESSAGE isn’t already defined.

#ifdef DEBUG
   /* Your debugging statements here */
#endif

It tells the CPP to process the statements enclosed if DEBUG is defined. This is useful if you pass the -DDEBUG flag to the gcc compiler at the time of compilation. This will define DEBUG, so you can turn debugging on and off on the fly during compilation.

ANSI C defines a number of macros. Although each one is available for use in programming, the predefined macros should not be directly modified.

  1. DATE The current date as a character literal in “MMM DD YYYY” format.
  2. TIME The current time as a character literal in “HH:MM:SS” format.
  3. FILE This contains the current filename as a string literal.
  4. LINE This contains the current line number as a decimal constant.
  5. STDC Defined as 1 when the compiler complies with the ANSI standard.

The C preprocessor offers the following operators to help create macros −

The Macro Continuation () Operator

A macro is normally confined to a single line. The macro continuation operator () is used to continue a macro that is too long for a single line. For example −

#define  message_for(a, b)  \
   printf(#a " and " #b ": We love you!\n")

The Stringize (#) Operator

The stringize or number-sign operator ( ‘#’ ), when used within a macro definition, converts a macro parameter into a string constant. This operator may be used only in a macro having a specified argument or parameter list. For example −

#include <stdio.h>
#define  message_for(a, b)  \
       printf(#a " and " #b ": We love you!\n")
int main(void) {
   message_for(Carole, Debra);
   return 0;
}

The Token Pasting (##) Operator

The token-pasting operator (##) within a macro definition combines two arguments. It permits two separate tokens in the macro definition to be joined into a single token. For example −

#include <stdio.h>
#define tokenpaster(n) printf("token" #n " = %d\n", token##n)
int main(void) {
    int token34 = 40;
    int token35 = 50;
    tokenpaster(34); // = printf ("token34 = %d", token34);
    tokenpaster(35);
    return 0;
}

The Defined() Operator

The preprocessor defined operator is used in constant expressions to determine if an identifier is defined using #define. If the specified identifier is defined, the value is true (non-zero). If the symbol is not defined, the value is false (zero). The defined operator is specified as follows −

#include <stdio.h>
#if !defined(MESSAGE)
    #define MESSAGE "You wish!"
#endif
int main(void) {
    printf("Here is the message: %s\n", MESSAGE);
    return 0;
}

Parameterized Macros

One of the powerful functions of the CPP is the ability to simulate functions using parameterized macros. For example, we might have some code to square a number as follows −

int square(int x) {return x * x;}

We can rewrite above the code using a macro as follows −

#define square(x) ((x) * (x))

Macros with arguments must be defined using the #define directive before they can be used. The argument list is enclosed in parentheses and must immediately follow the macro name. Spaces are not allowed between the macro name and open parenthesis. For example −

#include <stdio.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))
int main(void) {
    printf("Max between 20 and 10 is %d\n", MAX(10, 20));
    return 0;
}

C – Header Files

A header file is a file with extension .h which contains C function declarations and macro definitions to be shared between several source files. There are two types of header files: the files that the programmer writes and the files that comes with your compiler.

You request to use a header file in your program by including it with the C preprocessing directive #include, like you have seen inclusion of stdio.h header file, which comes along with your compiler.

Including a header file is equal to copying the content of the header file but we do not do it because it will be error-prone and it is not a good idea to copy the content of a header file in the source files, especially if we have multiple source files in a program.

A simple practice in C or C++ programs is that we keep all the constants, macros, system wide global variables, and function prototypes in the header files and include that header file wherever it is required.

Both the user and the system header files are included using the preprocessing directive #include. It has the following two forms −

#include <file>

This form is used for system header files. It searches for a file named ‘file’ in a standard list of system directories. You can prepend directories to this list with the -I option while compiling your source code.

#include "file"

This form is used for header files of your own program. It searches for a file named ‘file’ in the directory containing the current file. You can prepend directories to this list with the -I option while compiling your source code.

The #include directive works by directing the C preprocessor to scan the specified file as input before continuing with the rest of the current source file. The output from the preprocessor contains the output already generated, followed by the output resulting from the included file, followed by the output that comes from the text after the #include directive. For example, if you have a header file header.h as follows −

char *test (void);

and a main program called program.c that uses the header file, like this −

int x;
#include "header.h"
int main (void) {
   puts (test ());
}

the compiler will see the same token stream as it would if program.c read.

int x;
char *test (void);
int main (void) {
   puts (test ());
}

If a header file happens to be included twice, the compiler will process its contents twice and it will result in an error. The standard way to prevent this is to enclose the entire real contents of the file in a conditional, like this −

#ifndef HEADER_FILE
#define HEADER_FILE
the entire header file file
#endif

This construct is commonly known as a wrapper #ifndef. When the header is included again, the conditional will be false, because HEADER_FILE is defined. The preprocessor will skip over the entire contents of the file, and the compiler will not see it twice.

Sometimes it is necessary to select one of the several different header files to be included into your program. For instance, they might specify configuration parameters to be used on different sorts of operating systems. You could do this with a series of conditionals as follows −

#if SYSTEM_1
   # include "system_1.h"
#elif SYSTEM_2
   # include "system_2.h"
#elif SYSTEM_3
   ...
#endif

But as it grows, it becomes tedious, instead the preprocessor offers the ability to use a macro for the header name. This is called a computed include. Instead of writing a header name as the direct argument of #include, you simply put a macro name there −

#define SYSTEM_H "system_1.h"
...
#include SYSTEM_H

SYSTEM_H will be expanded, and the preprocessor will look for system_1.h as if the #include had been written that way originally. SYSTEM_H could be defined by your Makefile with a -D option.

C – Type Casting

Type casting is a way to convert a variable from one data type to another data type. For example, if you want to store a ‘long’ value into a simple integer then you can type cast ‘long’ to ‘int’. You can convert the values from one type to another explicitly using the cast operator as follows −

(type_name) expression

Consider the following example where the cast operator causes the division of one integer variable by another to be performed as a floating-point operation −

#include <stdio.h>
main() {
   int sum = 17, count = 5;
   double mean;
   mean = (double) sum / count;
   printf("Value of mean : %f\n", mean ); // Value of mean : 3.400000
}

It should be noted here that the cast operator has precedence over division, so the value of sum is first converted to type double and finally it gets divided by count yielding a double value.

Type conversions can be implicit which is performed by the compiler automatically, or it can be specified explicitly through the use of the cast operator. It is considered good programming practice to use the cast operator whenever type conversions are necessary.

Integer promotion is the process by which values of integer type “smaller” than int or unsigned int are converted either to int or unsigned int. Consider an example of adding a character with an integer −

#include <stdio.h>
int main() {
    int i = 17;
    char c = 'c'; /* ascii value is 99 */
    int sum = i + c;
    printf("Value of sum : %d\n", sum); //Value of sum : 116
}

The usual arithmetic conversions are implicitly performed to cast their values to a common type. The compiler first performs integer promotion; if the operands still have different types, then they are converted to the type that appears highest in the following hierarchy −

int->unsigned int->long->unsigned long->long long->unsigned long long->float->double->long double

The usual arithmetic conversions are not performed for the assignment operators, nor for the logical operators && and ||. Let us take the following example to understand the concept −

#include <stdio.h>
int main() {
    int i = 17;
    char c = 'c'; /* ascii value is 99 */
    float sum = i + c;
    printf("Value of sum : %f\n", sum); // Value of sum : 116.000000
}

Here, it is simple to understand that first c gets converted to integer, but as the final value is double, usual arithmetic conversion applies and the compiler converts i and c into ‘float’ and adds them yielding a ‘float’ result.

C – Error Handling

As such, C programming does not provide direct support for error handling but being a system programming language, it provides you access at lower level in the form of return values. Most of the C or even Unix function calls return -1 or NULL in case of any error and set an error code errno. It is set as a global variable and indicates an error occurred during any function call. You can find various error codes defined in header file.

So a C programmer can check the returned values and can take appropriate action depending on the return value. It is a good practice, to set errno to 0 at the time of initializing a program. A value of 0 indicates that there is no error in the program.

The C programming language provides perror() and strerror() functions which can be used to display the text message associated with errno.

The perror() function displays the string you pass to it, followed by a colon, a space, and then the textual representation of the current errno value.

The strerror() function, which returns a pointer to the textual representation of the current errno value.

Let’s try to simulate an error condition and try to open a file which does not exist. Here I’m using both the functions to show the usage, but you can use one or more ways of printing your errors. Second important point to note is that you should use stderr file stream to output all the errors.

#include <errno.h>
#include <stdio.h>
#include <string.h>
extern int errno;
int main() {
    FILE *pf;
    int errnum;
    pf = fopen("unexist.txt", "rb");
    if (pf == NULL) {
        errnum = errno;
        fprintf(stderr, "Value of errno: %d\n", errno);
        perror("Error printed by perror");
        fprintf(stderr, "Error opening file: %s\n", strerror(errnum));
    } else {
        fclose(pf);
    }
    return 0;
}

output:

Value of errno: 2
Error printed by perror: No such file or directory
Error opening file: No such file or directory

It is a common problem that at the time of dividing any number, programmers do not check if a divisor is zero and finally it creates a runtime error.

The code below fixes this by checking if the divisor is zero before dividing −

#include <stdio.h>
#include <stdlib.h>
int main() {
    int dividend = 20;
    int divisor = 0;
    int quotient;
    if (divisor == 0) {
        fprintf(stderr, "Division by zero! Exiting...\n");
        exit(-1);
    }
    quotient = dividend / divisor;
    fprintf(stderr, "Value of quotient : %d\n", quotient);
    exit(0);
}

It is a common practice to exit with a value of EXIT_SUCCESS in case of program coming out after a successful operation. Here, EXIT_SUCCESS is a macro and it is defined as 0.

If you have an error condition in your program and you are coming out then you should exit with a status EXIT_FAILURE which is defined as -1. So let’s write above program as follows −

#include <stdio.h>
#include <stdlib.h>
int main() {
    int dividend = 20;
    int divisor = 5;
    int quotient;
    if (divisor == 0) {
        fprintf(stderr, "Division by zero! Exiting...\n");
        exit(EXIT_FAILURE);
    }
    quotient = dividend / divisor;
    fprintf(stderr, "Value of quotient : %d\n", quotient);
    exit(EXIT_SUCCESS);
}

C – Recursion

Recursion is the process of repeating items in a self-similar way. In programming languages, if a program allows you to call a function inside the same function, then it is called a recursive call of the function.

void recursion() {
   recursion(); /* function calls itself */
}

The C programming language supports recursion, i.e., a function to call itself. But while using recursion, programmers need to be careful to define an exit condition from the function, otherwise it will go into an infinite loop.

Recursive functions are very useful to solve many mathematical problems, such as calculating the factorial of a number, generating Fibonacci series, etc.

The following example calculates the factorial of a given number using a recursive function −

#include <stdio.h>
#define N 200
long double fibonacci(unsigned int i) {
    static long double f[N];
    if (i <= 1) {
        f[i] = i;
    } else {
        if (f[i] == 0) {
            f[i]= fibonacci(i - 1) + fibonacci(i - 2);
        }
    }
    return f[i];
}
int main() {
    int i;
    for (i = 0; i < N; i++) {
        printf("%Lf\t\n", fibonacci(i));
    }
    return 0;
}

C – Variable Arguments

Sometimes, you may come across a situation, when you want to have a function, which can take variable number of arguments, i.e., parameters, instead of predefined number of parameters. The C programming language provides a solution for this situation and you are allowed to define a function which can accept variable number of parameters based on your requirement. The following example shows the definition of such a function.

int func(int, ... ) {
   .
}
int main() {
   func(1, 2, 3);
   func(1, 2, 3, 4);
}

It should be noted that the function func() has its last argument as ellipses, i.e. three dotes (…) and the one just before the ellipses is always an int which will represent the total number variable arguments passed. To use such functionality, you need to make use of stdarg.h header file which provides the functions and macros to implement the functionality of variable arguments and follow the given steps −

  • Define a function with its last parameter as ellipses and the one just before the ellipses is always an int which will represent the number of arguments.
  • Create a va_list type variable in the function definition. This type is defined in stdarg.h header file.
  • Use int parameter and va_start macro to initialize the va_list variable to an argument list. The macro va_start is defined in stdarg.h header file.
  • Use va_arg macro and va_list variable to access each item in argument list.
  • Use a macro va_end to clean up the memory assigned to va_list variable.

Now let us follow the above steps and write down a simple function which can take the variable number of parameters and return their average −

#include <stdarg.h>
#include <stdio.h>
double average(int num, ...) {
    va_list valist;
    double sum = 0.0;
    int i;
    /* initialize valist for num number of arguments */
    va_start(valist, num);
    /* access all the arguments assigned to valist */
    for (i = 0; i < num; i++) {
        sum += va_arg(valist, int);
    }
    /* clean memory reserved for valist */
    va_end(valist);
    return sum / num;
}
int main() {
    printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2, 3, 4, 5));
    printf("Average of 5, 10, 15 = %f\n", average(3, 5, 10, 15));
}

C – Memory Management

The C programming language provides several functions for memory allocation and management. These functions can be found in the header file.

  1. void *calloc(int num, int size); This function allocates an array of num elements each of which size in bytes will be size.
  2. void free(void *address); This function releases a block of memory block specified by address.
  3. void *malloc(int num); This function allocates an array of num bytes and leave them uninitialized.
  4. void *realloc(void *address, int newsize); This function re-allocates memory extending it upto newsize.

While programming, if you are aware of the size of an array, then it is easy and you can define it as an array. For example, to store a name of any person, it can go up to a maximum of 100 characters, so you can define something as follows −

char name[100];

But now let us consider a situation where you have no idea about the length of the text you need to store, for example, you want to store a detailed description about a topic. Here we need to define a pointer to character without defining how much memory is required and later, based on requirement, we can allocate memory as shown in the below example −

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
    char name[100];
    char *description;
    strcpy(name, "Zara Ali");
    /* allocate memory dynamically */
    // description = malloc(200 * sizeof(char));
    description = calloc(200, sizeof(char));
    if (description == NULL) {
        fprintf(stderr, "Error - unable to allocate required memory\n");
    } else {
        strcpy(description, "Zara ali a DPS student in class 10th");
    }
    printf("Name = %s\n", name);
    printf("Description: %s\n", description);
}

When your program comes out, operating system automatically release all the memory allocated by your program but as a good practice when you are not in need of memory anymore then you should release that memory by calling the function free().

Alternatively, you can increase or decrease the size of an allocated memory block by calling the function realloc(). Let us check the above program once again and make use of realloc() and free() functions −

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
    char name[100];
    char *description;
    strcpy(name, "Zara Ali");
    /* allocate memory dynamically */
    description = malloc(30 * sizeof(char));
    if (description == NULL) {
        fprintf(stderr, "Error - unable to allocate required memory\n");
    } else {
        strcpy(description, "Zara ali a DPS student.");
    }
    /* suppose you want to store bigger description */
    description = realloc(description, 100 * sizeof(char));
    if (description == NULL) {
        fprintf(stderr, "Error - unable to allocate required memory\n");
    } else {
        strcat(description, "She is in class 10th");
    }
    printf("Name = %s\n", name);
    printf("Description: %s\n", description);
    /* release memory using free() function */
    free(description);
    return 0;
}

C – Command Line Arguments

It is possible to pass some values from the command line to your C programs when they are executed. These values are called command line arguments and many times they are important for your program especially when you want to control your program from outside instead of hard coding those values inside the code.

The command line arguments are handled using main() function arguments where argc refers to the number of arguments passed, and argv[] is a pointer array which points to each argument passed to the program. Following is a simple example which checks if there is any argument supplied from the command line and take action accordingly −

#include <stdio.h>
int main(int argc, char *argv[]) {
    printf("Program name %s\n", argv[0]);
    if (argc == 2) {
        printf("The argument supplied is %s\n", argv[1]);
    } else if (argc > 2) {
        printf("Too many arguments supplied.\n");
    } else {
        printf("One argument expected.\n");
    }
}

It should be noted that argv[0] holds the name of the program itself and argv[1] is a pointer to the first command line argument supplied, and *argv[n] is the last argument. If no arguments are supplied, argc will be one, and if you pass one argument then argc is set at 2.

You pass all the command line arguments separated by a space, but if argument itself has a space then you can pass such arguments by putting them inside double quotes “” or single quotes ”.

分类
经济

2019-12-14-关于政府干预

今天看到一篇文章,是李子阳老师写的,

李子旸:香港社会制度的落后性

https://mp.weixin.qq.com/s/4LwTqbj5JbB87va4WuDsgQ

看了之后,我给奥派的张是之老师写了下面的留言。

张老师,我也自认是一个奥派。10年之前,开始看铅笔研究社的文章。之前李子阳老师也是铅笔研究社的主笔之一。但是我感觉他现在的观点与奥派的观点已经有点不一致了。

像这篇文章里面,他好像在主张政府需要规划和干预,而且似乎说的有道理,张老师怎么看呢?

奥派一直强调是政府不要干预市场。但是就像文中说的一样,如果都是一些小业主组成的团体,是很难投资基础设施建设,制定长远的科技发展计划的,而基础设施和长远的科技计划显然是对经济发展有利的。

举个例子,我舅舅所在的山村,一直想对外修一条水泥路。在我舅舅当村长以前,几十年都没有修成。等到我舅舅当村长以后,通过募捐,通过取得政府的支持,就把那条路给修通了,3.5公里的水泥路,总共耗资70万。

如果按照奥派说的不干预市场,虽然大家都想修路,但是根本没有领头的人,或者领头的人不给力,这条路就修不好。我舅舅其实后来从政府里面基本上没有拿到钱,只是口头上支持而已。最后的钱也是我舅舅,通过项目村民募捐做出来的,但是他有村长的身份,大家会给他面子。我不知道这算不算政府干预市场。

但是我觉得有的时候干预市场,真的是能够把一件做不成的事情变成能做得成。

现在那条路修好之后,山里面土特产往外运就方便很多了。如果按照奥派的说法,市场会自发地形成力量去修筑一条路。我是不敢赞同的。

比如跟我舅舅那个村子相邻的一个村子,那个路到现在还没有通。虽然那边出产的野樱桃比我们这边的还要好,如果修了路,他们肯定能赚的钱更多,但是就是没有人出头来干这件事情。

我觉得是不是存在这种这样一种情况,比如,村子里面有1000个人,修一条路能给每个人都带来1000块钱的好处,这样总共就是100万好处,但是修这条路需要70万,理论上每个人都出700块钱就能赚到1000块钱的好处,但实际上根本没有人愿意出头做这件事,因为个人得到的好处太少了,一旦有了政府或者是一个强势的个人出面做这件事情,大家其实是愿意做这件事的。在这种情况下,政府出面干预其实是对经济的发展有好处的。不知道张老师同不同意我的看法?

分类
编程

2019-12-13-the-c-programming-language-notes

The C Programming Language Notes

ch1 A Tutorial Introduction p22-p46

  1. Getting Started
    printf(“hello, world\n”);
  2. Variables and Arithmetic Expressions
    celsius = 5 * (fahr-32) / 9;
  3. The for statement
    for (fahr = 0; fahr <= 300; fahr = fahr + 20) printf(“%3d %6.1f\n”, fahr, (5.0/9.0)*(fahr-32));
  4. Symbolic Constants
    #define UPPER 300
  5. Character Input and Output c = getchar(); putchar(c);
    1. File Copying
    2. Character Counting
    3. Line Counting
    4. Word Counting
  6. Arrays
    int ndigit[10];
  7. Functions
    A function provides a convenient way to encapsulate some computation, which can then be used without worrying about its implementation.
  8. Arguments – Call by Value
    In C, all function arguments are passed “by value.” This means that the called function is given the values of its arguments in temporary variables rather than the originals.
  9. Character Arrays
    Character array is stored as an array of characters containing the characters in the string and terminated with a ‘\0’ to mark the end.
  10. External Variables and Scope
    An external variable must be defined, exactly once, outside of any function; this sets aside storage for it.

ch2 Types, Operators and Expressions p47-p63

  1. Variable Names
    Names are made up of letters and digits; the first character must be a letter. The underscore “_” counts as a letter; it is sometimes useful for improving the readability of long variable names. Don’t begin variable names with underscore, however, since library routines often use such names. Upper and lower case letters are distinct, so x and X are two different names.
  2. Data Types and Sizes
    There are only a few basic data types in C: char, int, float, double
  3. Constants
    A long constant is written with a terminal l (ell) or L, as in 123456789L; an integer constant too big to fit into an int will also be taken as a long. Unsigned constants are written with a terminal u or U, and the suffix ul or UL indicates unsigned long.
    The first name in an enum has value 0, the next 1, and so on, unless explicit values are specified.
  4. Declarations
    All variables must be declared before use, although certain declarations can be made implicitly by content.
  5. Arithmetic Operators
    The binary arithmetic operators are +, -, *, /, and the modulus operator %.
  6. Relational and Logical Operators
    The relational operators are > >= < <=. Expressions connected by && or || are evaluated left to right, and evaluation stops as soon as the truth or falsehood of the result is known.
  7. Type Conversions
    When an operator has operands of different types, they are converted to a common type according to a small number of rules.
  8. Increment and Decrement Operators
    The increment operator ++ adds 1 to its operand, while the decrement operator — subtracts 1.
  9. Bitwise Operators
    The bitwise exclusive OR operator ^ sets a one in each bit position where its operands have different bits, and zero where they are the same.
  10. Assignment Operators and Expressions
    i += 2 is equivalent to i = i + 2
  11. Conditional Expressions
    z = (a > b) ? a : b;
  12. Precedence and Order of Evaluation
    x = f() + g();
    f may be evaluated before g or vice versa; thus if either f or g alters a variable on which the other depends, x can depend on the order of evaluation.

ch3 Control Flow p64-p74

  1. Statements and Blocks
    x = 0; Braces { and } are used to group declarations and statements together into a compound statement, or block, so that they are syntactically equivalent to a single statement.
  2. If-Else
    if (a > b) z = a; else z = b;
  3. Else-If
    if (expression)
    statement
    else if (expression)
    statement
    else
    statement
  4. Switch
    switch (expression) {
    case const-expr: statements
    case const-expr: statements
    default: statements
    }
  5. Loops – While and For
    while (expression)
    statement
    for (expr1; expr2; expr3)
    statement
  6. Loops – Do-While
    do
    statement
    while (expression);
  7. Break and Continue
    The break statement provides an early exit from for, while, and do, just as from switch. The continue statement causes the next iteration of the enclosing for, while, or do loop to begin.
  8. Goto and labels
    C provides the infinitely-abusable goto statement, and labels to branch to.

ch4 Functions and Program Structure p75-p95

  1. Basics of Functions
    cc main.c getline.c strindex.c -o grep.exe
  2. Functions Returning Non-integers
    double atof(char s[])
  3. External Variables
    A C program consists of a set of external objects, which are either variables or functions.
  4. Scope Rules
    If an external variable is to be referred to before it is defined, or if it is defined in a different source file from the one where it is being used, then an extern declaration is mandatory.
    in file1: extern int sp;
    in file2: int sp = 0;
  5. Header Files
    #include “calc.h”
  6. Static Variables
    The static declaration, applied to an external variable or function, limits the scope of that object to the rest of the source file being compiled.
  7. Register Variables
    A register declaration advises the compiler that the variable in question will be heavily used.
  8. Block Structure
    Declarations of variables (including initializations) may follow the left brace that introduces any compound statement, not just the one that begins a function.
  9. Initialization
    In the absence of explicit initialization, external and static variables are guaranteed to be initialized to zero; automatic and register variables have undefined (i.e., garbage) initial values.
    char pattern[] = { ‘o’, ‘u’, ‘l’, ‘d’, ‘\0’ };// char pattern = “ould”; wouldn’t work.
  10. Recursion
    C functions may be used recursively; that is, a function may call itself either directly or indirectly.
  11. The C Preprocessor
    1. File Inclusion
      If the filename is quoted, searching for the file typically begins where the source program was found; if it is not found there, or if the name is enclosed in < and >, searching follows an implementation-defined rule to find the file.
    2. Macro Substitution
      The scope of a name defined with #define is from its point of definition to the end of the source file being compiled.
      #define max(A, B) ((A) > (B) ? (A) : (B))
      The expressions are evaluated twice; this is bad if they involve side effects like increment operators or input and output.
      #define dprint(expr) printf(#expr ” = %g\n”, expr)
      If a parameter name is preceded by a # in the replacement text, the combination will be expanded into a quoted string with the parameter replaced by the actual argument.
    3. Conditional Inclusion
      It is possible to control preprocessing itself with conditional statements that are evaluated during preprocessing.

ch5 pointer p96-125

  1. Pointers and Addresses
    int x=1, y;
    int *p; p=&x; y = *p;
  2. Pointers and Function Arguments
    void swap(int *px, int *py)
    swap(&a, &b);
  3. Pointers and Arrays
    int a[10], *pa, x;
    pa = &a[i]; // pa = a+i;
    x = *pa; // x= a[0];
    x = *(pa+1); // x = a[1]; x= *(a+1)
    f(&a[2]); // f(a+2)
  4. Address Arithmetic
    int *ptr; ptr = (int *)malloc(1024 * sizeof(ptr));
  5. Character Pointers and Functions
    char amessage[] = “now is the time”; /* an array */
    char *pmessage = “now is the time”; /* a pointer */
    void strcpy(char *s, char t){ while (s++ = *t++);
    }
  6. Pointer Arrays; Pointers to Pointers
    char lineptr[MAXLINES]; / pointers to text lines */
    lineptr[i] is a character pointer, *lineptr[i] is the character it points to.
  7. Multi-dimensional Arrays
    static char daytab[2][13] = {
    {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
    {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
    };
    int *daytab[13] is an array of 13 pointers to integers.
    daytab[i] is a integer pointer, *daytab[i] is the interger it points to.
  8. Initialization of Pointer Arrays
    char *month_name(int n)
    {
    static char *name[] = {
    “Illegal month”,
    “January”, “February”, “March”,
    “April”, “May”, “June”,
    “July”, “August”, “September”,
    “October”, “November”, “December”
    };
    return (n < 1 || n > 12) ? name[0] : name[n];
    }
  9. Pointers vs. Multi-dimensional Arrays
    int a[10][20]; //fixed size
    int *b[10]; // initialization must be done explicitly, either statically or with code.
  10. Command-line Arguments
    int main(int argc, char *argv[])
    For “echo hello, world” command, argc is 3, and argv[0], argv[1], and argv[2] are “echo”, “hello,”, and “world” respectively.
  11. Pointers to Functions
    void qsort(void lineptr[], int left, int right, int (comp)(void *, void *));
    int (*comp)(void *, void *) says that comp is a pointer to a function that has two void * arguments and returns an int.
    call pointer to function with
    (*comp)(v[i], v[left]) // comp(v[i],v[left]) also works, because a function’s name can also be used to get functions’ address.
  12. Complicated Declarations
    void comp() // comp: function returning pointer to void void (comp)() // comp: pointer to function returning void

ch6 struct p126-p146

  1. Basics of Structures
    struct point {int x; int y;} x, y, z; // similar to int x, y, z;
  2. Structures and Functions
    legal operations to a struct: copy, assign it as a unit, take address by &, accessing its member.
    struct point makepoint(int x, int y)
    {
    struct point temp;
    temp.x = x;
    temp.y = y;
    return temp;
    }
    struct point pp; access x by(pp).x or pp->x;
  3. Arrays of Structures
    struct key {
    char *word;
    int count;
    } keytab[NKEYS];
    //equals
    struct key {
    char *word;
    int count;
    };
    struct key keytab[NKEYS];
  4. Pointers to Structures
    struct key *binsearch(char *word, struct key *tab, int n)
    pointer arithmetic that involves the first element beyond the end of an array (that is, &tab[n]) will work correctly.
  5. Self-referential Structures
    struct tnode{
    char *word;
    int count;
    struct tnode *left;
    struct tnode *right;
    }
  6. Table Lookup
    struct nlist *lookup(char *s)
    for (ptr = head; ptr != NULL; ptr = ptr->next)
  7. Typedef
    typedef int Length;
    typedef char *String;
    String p, lineptr[MAXLINES], alloc(int);
    int strcmp(String, String);
    p = (String) malloc(100);
  8. Unions
    union u_tag {
    int ival;
    float fval;
    char *sval;
    } u;
    struct {
    char *name;
    int flags;
    int utype;
    union {
    int ival;
    float fval;
    char *sval;
    } u;
    } symtab[NSYM];
  9. Bit-fields
    struct {
    unsigned int is_keyword : 1;
    unsigned int is_extern : 1;
    unsigned int is_static : 1;
    } flags;
    flags that contains three 1-bit fields

ch7 Input and Output p147-p161

  1. Standard Input and Output
    while ((c = getchar()) != EOF) putchar(tolower(c));
  2. Formatted Output – printf
    int printf(char *format, arg1, arg2, …);
  3. Variable-length Argument Lists
    void minprintf(char *fmt, …)
  4. Formatted Input – Scanf
    int scanf(char *format, …)
  5. File Access
    FILE *fp;p
  6. Error Handling – Stderr and Exit
    fprintf(stderr, “%s: can’t open %s\n”, prog, *argv);
  7. Line Input and Output
    char *fgets(char *line, int maxline, FILE *fp)
    int fputs(char *line, FILE *fp)
  8. Miscellaneous Functions
    system(“date”);
    #define frand() ((double) rand() / (RAND_MAX+1.0))

ch8 The UNIX System Interface p162-178

  1. File Descriptors
    prog outfile
  2. Low Level I/O – Read and Write
    int n_read = read(int fd, char *buf, int n);
    int n_written = write(int fd, char *buf, int n);
  3. Open, Creat, Close, Unlink
    int open(char *name, int flags, int perms);
    int creat(char *name, int perms);
  4. Random Access – Lseek
    long lseek(int fd, long offset, int origin);
  5. Example – An implementation of Fopen and Getc
    FILE *fopen(char *name, char *mode)
  6. Example – Listing Directories
    void fsize(char *name)
    void dirwalk(char *dir, void (*fcn)(char *))
  7. Example – A Storage Allocator
    void *malloc(unsigned nbytes)

Appendix A: Reference Manual p179-p233

  1. Introduction
  2. Lexical Conventions
    There are six classes of tokens: identifiers, keywords, constants, string literals, operators, and other separators.
    Upper and lower case letters are different.
    Keywords auto double int struct break else long switch case enum register typedef char extern return union const float short unsigned continue for signed void default goto sizeof volatile do if static while
  3. Syntax Notation
    { expression\sub(opt) } means an optional expression, enclosed in braces.
  4. Meaning of Identifiers
    Identifiers, or names, refer to a variety of things: functions; tags of structures, unions, and enumerations; members of structures or unions; enumeration constants; typedef names; and objects.
    There are two storage classes: automatic and static.
  5. Objects and Lvalues
    An Object is a named region of storage; an lvalue is an expression referring to an object.
  6. Conversions
    Integral Promotion, Integral Conversions, Integer and Floating, Floating Types, Arithmetic Conversions, Pointers and Integers
  7. Expressions
    A pair of expressions separated by a comma is evaluated left-to-right, and the value of the left expression is discarded.
  8. Declarations
    In a declaration T D:
    When D is an unadored identifier, the type of the identifier is T.
    When D has the form ( D1 ) then the type of the identifier in D1 is the same as that of D. The parentheses do not alter the type, but may change the binding of complex declarators.
    int i, *pi, *const cpi = &i;
    const int ci = 3, *pci;
    declare an integer i and a pointer to an integer pi. The value of the constant pointer cpi may not be changed; it will always point to the same location, although the value to which it refers may be altered. The integer ci is constant, and may not be changed (though it may be initialized, as here.) The type of pci is “pointer to const int,” and pci itself may be changed to point to another place, but the value to which it points may not be altered by assigning through pci.
  9. Statements
    Except as described, statements are executed in sequence.
  10. External Declarations
    The unit of input provided to the C compiler is called a translation unit; it consists of a sequence of external declarations, which are either declarations or function definitions.
  11. Scope and Linkage
    A program need not all be compiled at one time: the source text may be kept in several files containing translation units, and precompiled routines may be loaded from libraries.
  12. Preprocessor
    A preprocessor performs macro substitution, conditional compilation, and inclusion of named files.
  13. Grammar
    A recapitulation of the grammar that was given throughout the earlier part of this appendix.

Appendix B – Standard Library p234-p296

  1. Input and Output: The input and output functions, types, and macros defined in represent nearly one third of the library.
    1. File Operations
    2. Formatted Output
    3. Formatted Input
    4. Character Input and Output Functions
    5. Direct Input and Output Functions
    6. File Positioning Functions
    7. Error Functions
  2. Character Class Tests:
    The header declares functions for testing characters.
  3. String Functions:
    There are two groups of string functions defined in the header . The first have names beginning with str; the second have names beginning with mem.
  4. Mathematical Functions:
    The header declares mathematical functions and macros.
  5. Utility Functions:
    The header declares functions for number conversion, storage allocation, and similar tasks.
  6. Diagnostics:
    The assert macro is used to add diagnostics to programs.
  7. Variable Argument Lists:
    The header provides facilities for stepping through a list of function arguments of unknown number and type.
  8. Non-local Jumps:
    The declarations in provide a way to avoid the normal function call and return sequence, typically to permit an immediate return from a deeply nested function call.
  9. Signals:
    The header provides facilities for handling exceptional conditions that arise during execution, such as an interrupt signal from an external source or an error in execution.
  10. Date and Time Functions:
    The header declares types and functions for manipulating date and time.
  11. Implementation-defined Limits: and
    The header defines constants for the sizes of integral types. The values below are acceptable minimum magnitudes; larger values may be used.
分类
编程

2019-12-01-twitter视频下载

今天在twitter上面看到一个好玩的视频,我就想下载下来,结果折腾了几个小时。

首先看网页代码,视频那里是一个blob:

<video preload="none" playsinline="" aria-label="Embedded video" poster="https://pbs.twimg.com/ext_tw_video_thumb/1200926756321951745/pu/img/l-V1sNMeAZL5tcGN.jpg" src="blob:https://twitter.com/dd866381-2ada-4c2b-80af-9cec2a580039" style="width: 100%; height: 100%; position: absolute; background-color: black; top: 0%; left: 0%; transform: rotate(0deg) scale(1.005);"></video>

我google了一下,这是流媒体的格式,要下载的话需要找到对应的m3u8文件,就是在chrom浏览器的Network里面过滤m3u8. 看到一个https://video.twimg.com/ext_tw_video/1200926756321951745/pu/pl/460×258/Kuir9CTQnfaw-EWA.m3u8

Google说可以用youtube-dl下载,然后我先去下载这个玩意,因为还有同学说可以直接用ffmpeg转换格式。

等youtube-dl下载完了之后,我运行下面的命令:

./youtube-dl -f mp4 -o baby.mp4 https://video.twimg.com/ext_tw_video/1200926756321951745/pu/pl/460x258/Kuir9CTQnfaw-EWA.m3u8

结果提示需要ffmpeg或者avconv,尼玛还是得安装ffmpeg。

然后我就老实的brew install ffmpeg。完事之后。

运行:

./youtube-dl -f mp4 -o baby.mp4 https://video.twimg.com/ext_tw_video/1200926756321951745/pu/pl/460x258/Kuir9CTQnfaw-EWA.m3u8

结果卡在那儿不动弹。

我就试一下直接用ffmpeg转换:

ffmpeg -i 'https://video.twimg.com/ext_tw_video/1200926756321951745/pu/pl/460x258/Kuir9CTQnfaw-EWA.m3u8' -vcodec copy -acodec copy -absf aac_adtstoasc o.mp4

也是一样的,下到一半卡在那儿不动弹。

我就换了个思路,想先看看m3u8里面的内容,

wget https://video.twimg.com/ext_tw_video/1200926756321951745/pu/pl/460x258/Kuir9CTQnfaw-EWA.m3u8
cat Kuir9CTQnfaw-EWA.m3u8

结果是这样:

#EXTM3U
#EXT-X-VERSION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-TARGETDURATION:4
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-ALLOW-CACHE:YES
#EXTINF:3.000,
/ext_tw_video/1200926756321951745/pu/vid/0/3000/460x258/G5KaOzbFFR97qdzl.ts
#EXTINF:3.000,
/ext_tw_video/1200926756321951745/pu/vid/3000/6000/460x258/4lEqbRtnJ2YtXHRw.ts
#EXTINF:3.000,
/ext_tw_video/1200926756321951745/pu/vid/6000/9000/460x258/uSvCKT4MckCDGKSf.ts
#EXTINF:3.000,
/ext_tw_video/1200926756321951745/pu/vid/9000/12000/460x258/QSnzs6dHUar2OXfY.ts
#EXTINF:3.000,
/ext_tw_video/1200926756321951745/pu/vid/12000/15000/460x258/zdytmI8intABhyFW.ts
#EXTINF:3.000,
/ext_tw_video/1200926756321951745/pu/vid/15000/18000/460x258/Gh8owrBVf_s8SwDs.ts
#EXTINF:3.000,
/ext_tw_video/1200926756321951745/pu/vid/18000/21000/460x258/GWnvSye6J8mkjr35.ts
#EXTINF:3.000,
/ext_tw_video/1200926756321951745/pu/vid/21000/24000/460x258/Bmd-gN7sTAtj_bme.ts
#EXTINF:3.000,
/ext_tw_video/1200926756321951745/pu/vid/24000/27000/460x258/lkTRSPSKrGpFB_h6.ts
#EXTINF:3.000,
/ext_tw_video/1200926756321951745/pu/vid/27000/30000/460x258/DcZQHhRTJOJ-HNyJ.ts
#EXTINF:3.000,
/ext_tw_video/1200926756321951745/pu/vid/30000/33000/460x258/-3GD9_NdfoDUVF9q.ts
#EXTINF:3.000,
/ext_tw_video/1200926756321951745/pu/vid/33000/36000/460x258/WuuI_REDOMnYjzSU.ts
#EXTINF:3.958,
/ext_tw_video/1200926756321951745/pu/vid/36000/39958/460x258/AZrLcuvgp5XsaOcU.ts
#EXT-X-ENDLIST

我就写了个脚本wget.sh先把这个视频下载下来再转换:

wget https://video.twimg.com/ext_tw_video/1200926756321951745/pu/vid/0/3000/460x258/G5KaOzbFFR97qdzl.ts
wget https://video.twimg.com/ext_tw_video/1200926756321951745/pu/vid/3000/6000/460x258/4lEqbRtnJ2YtXHRw.ts
wget https://video.twimg.com/ext_tw_video/1200926756321951745/pu/vid/6000/9000/460x258/uSvCKT4MckCDGKSf.ts
wget https://video.twimg.com/ext_tw_video/1200926756321951745/pu/vid/9000/12000/460x258/QSnzs6dHUar2OXfY.ts
wget https://video.twimg.com/ext_tw_video/1200926756321951745/pu/vid/12000/15000/460x258/zdytmI8intABhyFW.ts
wget https://video.twimg.com/ext_tw_video/1200926756321951745/pu/vid/15000/18000/460x258/Gh8owrBVf_s8SwDs.ts
wget https://video.twimg.com/ext_tw_video/1200926756321951745/pu/vid/18000/21000/460x258/GWnvSye6J8mkjr35.ts
wget https://video.twimg.com/ext_tw_video/1200926756321951745/pu/vid/21000/24000/460x258/Bmd-gN7sTAtj_bme.ts
wget https://video.twimg.com/ext_tw_video/1200926756321951745/pu/vid/24000/27000/460x258/lkTRSPSKrGpFB_h6.ts
wget https://video.twimg.com/ext_tw_video/1200926756321951745/pu/vid/27000/30000/460x258/DcZQHhRTJOJ-HNyJ.ts
wget https://video.twimg.com/ext_tw_video/1200926756321951745/pu/vid/30000/33000/460x258/-3GD9_NdfoDUVF9q.ts
wget https://video.twimg.com/ext_tw_video/1200926756321951745/pu/vid/33000/36000/460x258/WuuI_REDOMnYjzSU.ts
wget https://video.twimg.com/ext_tw_video/1200926756321951745/pu/vid/36000/39958/460x258/AZrLcuvgp5XsaOcU.ts

下载下来之后,因为m3u8里面的路径也要改一下:
把前面的“/ext_tw_video/1200926756321951745/pu/vid/33000/36000/460×258/”这一串也去掉。变成k.m3u8。

#EXTM3U
#EXT-X-VERSION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-TARGETDURATION:4
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-ALLOW-CACHE:YES
#EXTINF:3.000,
G5KaOzbFFR97qdzl.ts
#EXTINF:3.000,
4lEqbRtnJ2YtXHRw.ts
#EXTINF:3.000,
uSvCKT4MckCDGKSf.ts
#EXTINF:3.000,
QSnzs6dHUar2OXfY.ts
#EXTINF:3.000,
zdytmI8intABhyFW.ts
#EXTINF:3.000,
Gh8owrBVf_s8SwDs.ts
#EXTINF:3.000,
GWnvSye6J8mkjr35.ts
#EXTINF:3.000,
Bmd-gN7sTAtj_bme.ts
#EXTINF:3.000,
lkTRSPSKrGpFB_h6.ts
#EXTINF:3.000,
DcZQHhRTJOJ-HNyJ.ts
#EXTINF:3.000,
-3GD9_NdfoDUVF9q.ts
#EXTINF:3.000,
WuuI_REDOMnYjzSU.ts
#EXTINF:3.958,
AZrLcuvgp5XsaOcU.ts
#EXT-X-ENDLIST

然后再来用ffmpeg -i k.m3u8 k.mp4,就搞定了。

总结一下, ffmpeg这个肯定是需要安装的,youtube-dl是调用这个的。如果网络给力的话,youtube-dl应该可以直接完成转换,但是我试了两次就没成功,所以用wget先把ts文件下载下来再转换。