Instantly share code, notes, and snippets.

Embed
What would you like to do?
贴吧相关 FAQ 、资源链接和其它附带清单。

贴吧历史资源整理

吧务操作

http://tieba.baidu.com/p/4171474077

日常(经)整理

http://tieba.baidu.com/p/2286985797

头衔

http://tieba.baidu.com/p/1293939748 C++吧的头衔勋章应该能改了吧

http://tieba.baidu.com/p/1294795004 【征集】C++吧头衔名征集帖。

http://tieba.baidu.com/p/1297445553 C++吧头衔碉堡了

http://tieba.baidu.com/p/2124684426 咱就不能有个正常点的会员名称吗?

http://tieba.baidu.com/p/2484488705 请问咱们吧的头衔亮出**cm是什么意思呢

http://tieba.baidu.com/p/2497542074 新人刚来 头衔好奇怪…………

http://tieba.baidu.com/p/2190079071 新人报道,话说这神奇的头衔是怎么回事→_→

http://tieba.baidu.com/p/2601257047 我非常迫切的想知道,咱吧里的会员头衔是什么个意思,亮出...C

0cm

<15cm

=15cm

似乎是 http://tieba.baidu.com/p/1820200287 ,已炸。

>15cm

关于头衔的外吧评论

会员名

个人 FAQ

头像

http://tieba.baidu.com/p/1291218344

学习历程

http://tieba.baidu.com/p/3388459316

基本法

操作示例

部分引用置顶索引贴 http://tieba.baidu.com/p/2678895259

例 1

[吧务操作]
解释备案。
依据现行吧规(git版本81e9aa0a0eccc127fa87ecb99b506b65c3c332ea,最后修订20140322),对本帖操作解释如下。
A.鉴于由LZ本人提交给吧务的证据,兹裁定LZ在本贴以前违反以下条款:
3.10。
B.兹裁定本贴LZ违反以下条款:
1.1、1.3、1.4、4.3、B.7、B.8。
C.违规认定和正文条款处理意见:
由条款2.5,判断LZ已经构成违规。依据2.1、2.3、2.5条款,对LZ违反3.10的处罚已经先期执行,对其它违规行为暂予保留2.4条款执行权力。
D.B条款处理意见:
操作者认为此贴意义不足以挂城门。
操作者认为此贴可以于不特定时间不解释删除,但暂缓执行。
[吧务操作]
补充30L说明。
E.25L建议按2.1条款仍然有效。不确定时间生效。
F.鉴于保留处理不属于不解释事项(2.9条款定义),兹解释保留暂缓处理的理由如下:
F.1 涉嫌多项违规,在30L明确行为前可认为不宜直接合并处理。
F.2 涉嫌傀儡账号(2.4条款定义)。对傀儡账号是否需要和LZ此帐号合并处理有待审议。
F.3 涉嫌的事由涉及操作者以外的吧务。尽管事实清楚,操作者仍然认为有必要转达处理现场。通知已经发放,待结案。
F.4 操作者认为此案例具有现实教育和阻止继续违规的意义,可以重申私信骚扰对于合理诉求无效这一常识性问题。
F.5 操作者认为此案处理过于仓促,不足以发挥惩戒的威慑作用;操作依赖F.1和F.2,仍不足以构成判例。

例 2

[吧务操作]再次提醒,暴力膜吱也须遵守基本法。
作为管理者,吧务希望吧友能够亲切友好的交谈,或者至少能坦率交谈。因为一般来说,会谈是有益的。
本吧对主题内容的要求和限制整体上并不严格。回复中个别人交换了意见,即便和主题内容无关,吧务原则上也没有必要干涉。当然,明显超出必要的限度,过于充分地交换了意见而造成不愉快,不管是否增进了双方的了解,都可能适用吧规处理而限制权利。
在合理合法的前提下,我们尊重吧友的言论自由权利,赞赏强调遵守规则的讨论者。对于非明令禁止以避免损害吧友利益的个人观点,我们持保留态度。在规则限定外,若产生误会,只能表示遗憾。
我们历来会对在规则之外没事找事、套用双重标准、道德绑架的讨论者表示极大的愤慨,并对可能发生的违规行为表示严重关切。特别地,单方面主张自身权利而无视他人权利者,不能置之不理。对所有这些不利于正常讨论氛围的情况,吧务保留做出进一步反应的权利。
考虑到出错可能在所难免,我们有限地允许关于言论中的不当内容,并留出适当时间做出修正。必要时,吧务可能限定时间限制。在进一步回复后,我们将重新考虑这一问题的立场。
无视客观事实、颠倒黑白和煽动群众分裂吧友阵营是我们万万不能容忍的;这是不友好的行动,是可忍孰不可忍。
我们敦促某些挑衅者悬崖勒马。执迷不悟者,不妨拭目以待,希望本吧的立场给你们能留下深刻印象。这对社会资源来说是也有益的,所以道德制高点基本上也自然失效了——换言之,请不要继续无谓地挣扎;否则,请自行负责由此引起的后果。勿谓言之不预也。

题外话,这样一说为什么到此为止这里瞎bb跳脚的不是“路人”就是有据可查作奸犯科嫌疑者?或者说“路人”就能和当权者划清界限于是乎了不起?也罢,看了这几个所谓的“路人”,要是真能代婊路人的平均水平,我以后都不好意思拿自己当路人逛别的吧示众了……
用祈使句前请搞清楚自己的能耐,不要别人科普权利和义务的有关常识。

借LZ贴,以下对所有自我意识过剩目无法纪好为人师没事瞎bb的逗比宣战。

(因为这个话题太low所以不好意思开新主题。看LZ先前积极参与的态度,放在这里大概也不算污染主题。)哪个想对号入座不满的,除了自挂到http://tieba.baidu.com/p/2390940218,到这里回复能澄清自己立场的不违反吧规的仍然保留。不过既然有够多敬酒不吃吃罚酒/给脸不要脸的例子,之后我也懒得警告,直接专政到能认识到错误、自觉闭嘴为止。到底战斗力过剩,拭目以待。

 [吧务操作]鉴于此类主题的内容和影响,拿基础学科话题解释而不是纯水的,注意尽量做到以下几点,否则同吧规4.6并从严处理:
1 明确讨论的具体对象的外延,以及和主题之间的关联。
1.1 一般应指出具体的学科分支以及直接影响,能让读者推断重要性之理由。
1.2 区分该学科的理论和课程。能让读者明确了解知识点依赖和学习的困难程度。
1.3 明确讨论的理论分支和整个学科的联系,严禁以偏概全。如有必要,明确该分支适用的范围。
2 确保可能发生争议的观点可以查证。
2.1 区分原创研究和公认事实。
2.2 没有把握或存疑的观点应明确指出。
2.3 必要时引用文献。

惩治戡乱纪要

例 1

http://tieba.baidu.com/p/3716248078

(因为内文含文本转义记号,分别加注。)

这个主题保留。

正好挂人,我懒得开新主题污染版面。

首先,关于LZ的一些“为什么学”的问题:有些人蠢惯了,也喜欢你们跟着一起蠢,这样谁都“方便”。然而只要制造了些玩具扩散了,擦屁股的人还是要有的,只不过怎么也轮不到没能耐的蠢人。

yucwei:        回复 幻の上帝  看来你是很懂了,那我问你一个问题,解决一个问题是用简单易懂的方法好,还是用只有你自己明白的不知道从哪里抠出来的歪门邪道的方法好?你所谓的懂就是一些不入流的,拿不到堂面,不值一提偏道技巧而已。

做个典例挂着。楼中楼回复不够显要,重复一遍:

幻の上帝:   回复 @yucwei   :你爱怎么解决就怎么解决。但是当你提出一个足够蠢的问题却还掩饰欺骗别人说这不蠢时,你浪费的别人的时间和精力就比你无论怎么解决的价值更大,那么就老 实挨打吧。好好反省一下懂一般需求的正常用户和只懂其中一小撮子集的逗比用户评论解决一般问题入不入流这种破事上,谁更有说服力呢。

然后要补充的是分析一下行为动机和额外结论。
先说结论:蠢货多数是牺牲品,然而另一方面又是祸害——只是因为收拾起来太麻烦了所以才能苟延残喘——这里应该也不例外。
分析理由:
首先这些人入门的时候就被某些现成的蠢货推销了挂羊头卖狗肉的黑材料,以为他们“学会”了某些真的“有用”的东西。
恰好C这样的语言因为设计和历史原因,特别适合造成这种错觉。实际上他们不是学会了真正被普遍承认的、在现实中发挥作用的C(羊头),而是一种按照个人智商局限下曲解以后的方言(狗肉)。
不巧,这些黑材料的作者自己并没有能耐实现语言(写个编译器啥的),自然更不可能改变工业界会用的解决方案了。
而某些黑材料又特别乐于或者只擅长于把C++阉成那种不伦不类的玩意儿。
于是这些黑材料的读者就被迫在微妙对不上号的现实实现和黑材料本身做出取舍。可能智商局限性会传染,缺乏其它补充材料对比时,就更容易盲信了。
比如说,当发现编译运行结果不符合预期,就自行脑补“原理”,以为靠自己眼前的个别现象就能归纳出普遍规律;甚至选择性无视,忽略和期望不符的事实自欺欺人。
可惜不管语言还是语言的实现,都是人为设计的结果。这种一厢情愿的学习方法一开始就是图样逗比的笑话。于是当没有正常靠谱点的材料撑腰又不知道对比和直面无知时,姿势当然正确不到哪里去。
对着C/C++这类敢于规定UB的语言这样YY,不仅是给自己挖坑,还给别人制造擦屁股的成本。(重复,智商局限性有传染的风险。)

无知是没问题,卖蠢还真是敬谢不敏。

然后,关于陈良乔。
我为啥要说这个人呢——大概是因为某些方面也比较极端吧。
@人类的潜力 :        回复 用微笑释怀岁月  :如果觉得自己技术可以,推荐c++primer,或者2本都买,再给你推荐本国内牛人写的《我的第一本c++书》
正好有人提到了,看来也不算有意误导那就回一下。
首先,他那本《我的第一本C++书》的逗程度应该还是有点知名度的。
(其实以前也说过了:http://tieba.baidu.com/p/1519041467。)
要真以这本书的水平把所谓的“牛人”划分成一个等价类,我觉得这快到人身攻击了。所以就吐槽了。
客观地说,这人应该也不是完全有胆子像谭×自信爆棚乱跑火车,所以有时候翻译Bjarne Stroustrup的话啥的也没敢夹带私货。不过原创材料基本就只是笑话了。
这个人和我没啥私怨,不过给我和许多其他人的印象很糟糕。我能记得他的唯一一个突出优点就是说话能不带脏字,而骨子里十动然拒就是死皮赖脸,被指出硬伤以后就是当作没有造成伤害判定。换句话说,厚脸皮。
具体一点,要八卦的:
http://bbs.chinaunix.net/thread-3692320-1-1.html
http://bbs.chinaunix.net/thread-3772840-1-1.html
当然,图书市场编辑水准啥的这个也习惯了,另当别论,这里不多展开。
另外一个八卦是陈良乔和某个不入流的民科以下水准自称“人宇科学”的奇葩团体有一些看来说不大清楚的关联。
看来对一般的中老年人都没啥传教效果……这使我对其信众的素(智)养(商)判定降低了30点偏差值。

TODO

http://tieba.baidu.com/p/5521840151

贴吧官方作死

就这点斤两也想 70W 大戏,呵呵呵…… (结果是 4W 都没人要……)

“很遗憾地告诉您,您因不符合我们的要求申请未被通过。可能在以下方面需要加强哦~”
呵呵……
“1、提高您的活跃度,多登录,多发言”
请指教每月90%登录的Lv14的2.8w贴还差多少?
“2、提高您的发言质量,积极参与有意义的言论建设,不要为刷等级而灌水”
咦这里有谁敢刷等级灌水?
“3、使用文明的用户名”
不文明的用户名不直接干掉居然还能留着允许点申请吧主?

待定整理内容

待补充

const/常量表达式。

参考:

https://github.com/FrankHB/pl-docs/blob/master/zh-CN/variables.md

指引和科普

文献相关

C++入门书?Bjarne Stroustrup的The C++ Programming Language,虽然我也没耐心看几百页就去直接读ISO C++了。

嫌难就找Bjarne Stroustrup的Programming: Principles and Practice Using C++,对多数人来讲应该更容易点。The design & evolution of C++这本不是入门的,但研究语言设计理论的,可以当消遣性历史读物,用于通过自测不看书之前的理解是否对路帮助评判自己悟性如何。

以下只能给出常识性评价。

首先,权威文档是 ISO C 和 ISO C++ 。有错误可以翻 open-std.org 的 defect report 和 paper 考古。其它靠谱点的书基本都有 errata ,不过相对没那么容易及时修bug。

C的话就是 K&R 的 The C Programming Language ,不过也可能有一些错误,所以随时准备考虑 ISO C 修正。

C++入门的除了 BS 也就 Scott Meyers 的书稍微能看点。 Herb Sutter 和 Andrew Koenig 也算稍微高级那么点的话题的靠谱的作者(也包括一些关于 C 的说法)。这里提一下 Accerated C++ 风评还可以,但是应该比较旧了。相对来讲一些讲C++惯用法的书([More] Efficient/Exceptional C++)和以及讲C相关的书如 C Traps and Pitfall 这种相对不那么容易过时,特别是后者——因为语言在这方面变动比较少。

不推荐 C++ Primer 的原因是 Lippman 的文笔以及一些其它编排问题。以及读者认知偏差会比其它正经经典书更多。

Primer 后带 Plus 的不管是 C 还是 C++ 都是著名李鬼。作者应该不太懂关键知识点。

谭×之类的国产作者写这类书一般不作考虑——如果你不想返工重来一遍的话。尽管书本身的销量可能最多。

另外这里一个经验之谈是,如果一个作者既写 C++ 又写 Java 或者其它语言的基础教材,那么基本上至多只会有其中一门语言稍微靠谱点。比如谭×也就是写 BASIC 的书还行。当然,更可能的是没一本书靠谱。因为写靠谱的作者需要同时对不同语言的 spec 有深入准确的理解,这类作者一般不会去有空写这种书。(换做是我,要不是需要重复科普废话实在太多,带人灌水 paper 都来不及呢……)

如果有能力读原著,那就考虑直接读原著,因为译者的下限会把书的内容质量下限再过滤一遍,而译者普遍水平比坐着更差,虽然不排除个别还算靠谱的。尤其应慎重选择教科书的译著以免先入为主。裘宗燕的翻译质量整体还行,但有些术语明显偏离惯用法。其它自己搜风评。

TODO 可以加不少链接。

范型

所谓函数(function)

Q:

http://tieba.baidu.com/p/5933093123

A:

和数学中通常意义的函数有类似性但关系不大,不过可能会在问题的描述中出现。

中学数学中的所谓函数是限制定义域和值域为实数集的单值全函数的狄利克雷定义表述。更一般的数学会引入陪域、非全函数(偏函数)、非单值函数(多值函数)、非实数集的定义域和值域(包括所谓的高阶函数,数学的行话叫泛函)、非纯函数(求值函数具有副作用)。最后一点是一般人最需要注意的区别,因为C++不是所谓的纯函数式语言;而大多数搞数学乃至搞所谓理论计算机科学的也不会接触包括非纯函数的模型(例如某种lambda演算的扩展),所以不管有没有数学基础一般都得从头学。

补充:

关于函数的定义有一整坨历史问题,可以参考英文维基页面

数学中所谓的函数是理论计算机科学(精确点说,模型论和递归论,其实还是数学,并且是最基本的数学)的一类研究对象。这个意义上来讲,最一般的函数概念都是共通的。不过数学中更常用的函数概念是其中所谓的纯函数,即限制变换为表达式之间的表示的变换,这基于映射或者数学关系来直接定义。引入副作用的函数则通常需要在某种重写系统(一般称为演算(calculus) )上形式化其操作语义来定义。这种一般的函数和纯函数的求值一样,被“调用”,然后表达重写系统的替换规则。纯函数的调用引起值的替换;而一般的函数调用可能有其它的副作用,例如引起外部状态的改变。以这样的一般函数作为基本操作的形式系统都可以称为所谓的函数式(functional) 的。

一些语言使用简化的语义,使之和传统的数学中的函数类似。这种方式成为纯函数式(pure functional) 的范型。这样的语言被普遍称为纯函数式语言。其主要优点是默认具有引用透明性(reference transparency) ,可以相对自由地用代数的方式变换等价的程序而不需要担心副作用顺序的差异而不得不关心表达式局部的求值顺序。不过,对实际应用需要操作状态(如输入/输出)的需求,纯函数式语言通常不能直接解决,而需要一些变通:例如,使用单子(monad) 限制副作用产生的位置,使程序整体仍然保持纯函数式的性质。

对大多数入门者而言,他们通常没有机会接触到一般的数学模型,也不大会接触到纯函数式语言。常见的普遍的命令式(imperative) 语言都支持副作用,而不保证使用纯函数式范型。因此,需要清醒地了解和他们学习过的数学中的函数有很大不同——语义上共性可能仅仅只有“输入”参数、进行计算、“输出”值的过程而已(此处的输入输出不是严格的说法,和改变系统状态的操作无关)。常见的不少语言可能还照搬了数学中习惯使用的函数表达式的语法,以及计算结果只能有单个值的惯例(一般意义上,这样的限制是不必要的,这取决于语言的设计风格);如果计算过程中没有副作用,则定义函数的计算步骤和数学意义上的函数可以类似。

计算理论

所谓的递归,一般地,指的是非直谓性(impredicativity)的陈述形式。

非直谓式的形式中含有有自我指涉(self-reference)而无法直接通过体系外的内容提供完全的替代。为了让这些陈述能被理解为表示现实问题或它们对应的解,通常需要附加约定称为递归出口的条件,以保证递归按递归被理解时可以终止。从递归出口逆向得到符合递归定义的任意状态的构造或求解过程称为递推。

可以证明,具有递归构造的一些形式系统(如μ-递归函数)在表达形式的问题上具有和图灵机等价的计算能力。对递归形式的变换和抽象机中的特定操作可以存在等价关系。

为什么需要递归?不管是理论还是现实问题,很多表述都具有清晰的递归形式,甚至有些只能用递归才能合理(比如说,“写得下”)地描述清楚。与此对应,这些问题中的典型的解也具有递归的结构:不得不通过递推构造逐步得到。

指令式语言常见的循环结构等价于其中的一类特例,即尾递归。但并不是所有递归形式都能退化为尾递归,不能指望总是能用直觉上便于理解的循环来取代递归。这个意义上的循环本质上只是一种递归的优化。因此,在一些语言中,递归被作为基本的控制结构,然后通过尾调用优化在生成代码中转换为对存储资源占用可预测性友好(也利于指令式语言的机器执行和优化)的“直接”的循环。

像类C语言所谓的递归函数,并不是指上述可计算理论中的递归函数,而是其中的特例——(有限)嵌套调用的函数调用表达式。(特别注意,这些语言的“函数”通常就不是一等表达式,和更一般的系统如lambda calculus中的lambda abstraction相比,具有一些明显限制,本身就是一般“函数”的弱化版。)具体地,在一个函数调用表达式如f(a, b)求值时,其中的f可能等价于正在被调用的函数自身——这就是一种自我指涉,这种调用就是递归调用。注意递归调用只是语言(普遍)支持的便利特性,实现递归的计算形式不一定需要语言直接支持递归调用(比如早期的Fortran)。

题外话:

因为现实机器不可能具有无限的存储,而并非每一次调用都能不消耗额外的存储资源,语言的实现必须有数量上的限制(implementation quantities) 。(事实上因为同样的理由,物理机器的计算能力严格地弱于图灵机。)对于C++这样的规定具有静态翻译phase的语言,明确在翻译时给出了一些要求,包括模板实例化和递归constexpr调用的嵌套层数——不满足这些要求的就不算C++的实现。而剩下的运行环境限制,语言规范本身不进行进一步限制来界定符合性(conformance)。

http://tieba.baidu.com/p/5892731666

首先你搞错了概念,递归函数是递归论研究的计算对象,一般(μ-)递归函数跟图灵机可计算性等价。递归调用跟递归函数是两回事。

其次,你恰恰搞反了,递归调用的函数显然一般不是“逆向思维”。递归函数往往是问题的解的直接描述,因为所有可计算问题的解和求解过程都能用递归函数表示,进而对应到递归的(过程)调用上——只不过有些语言原生对递归调用有限制或者实现起来低效所以才有必要变通。反过来,只有少数问题才有一眼就能看出特殊形式的递归(等价于递推)关系的解,其它问题的解都需要用逆向思维把一般递归函数的解转换为这类特殊递归函数。

这种特殊递归形式称为尾递归(tail recursion) ,计算复杂度等价于循环。本质上,把一般递归形式的解变换为尾递归的解和优化为循环形式的解是相同的过程,只不过用来表达结果的语言使用的具体实现形式不同(尾调用或者循环关键字)。

如果在乎的是算法或者一般的解的结构,这里的区别可以无视;不过你至少需要知道,在只有循环关键字而没有一般尾调用和一等续延(first-class continuation) 支持的语言中,直接表达这类特殊递归的形式的解是无能的,遇到一些典型问题(如遍历一个没有退化成线性表的树的回溯(backtracking) 算法)如果不用原生递归调用,可移植的实现方式就只有显式做CPS变换(continuation-passing style transformation)改写代码暴露活动记录(activation record) 为调用的参数。反过来,对支持这样的特性或者一些在这个问题上至少使用起来有同等能力的特性(比如某些语言的monad或者computational expressions)的语言来讲,你就不需要做这种人肉编译器的体力活,因为这种操作是语言实现的分内事,并且编译器可以用不可移植的手段使用比CPS变换更有效率的方式。很不幸,至少在P0534被接受之前,C++是前者。后者的主要代表是Scheme、Haskell、F#等所谓FP语言。

最后值得一提的是原生递归调用的限制。如果一个语言没有一等续延或者等价表达能力的特性而有足够强大的原生递归调用支持(具体而言,proper tail recursion,如Scheme和ML要求的那样,或者更一般地称为PTC(proper tail call) ),那么就表达问题上可能不是很大(实际可能因为算法复杂度太差而不可用,需要手动改写为循环)。ISO C和ISO C++是这里特别糟糕的例子,因为语言规则不但不要求PTC,还对嵌套调用的深度不做可移植性的保证(注意这不只是递归调用的问题):任意超出若干深度的调用引起未定义行为,程序是不可移植的。最烂的是,这里嵌套深度没个准,不可能用可移植的方式获得,你只能祈祷任何敢让人用的实现在“日常”操作下都不会出问题。考虑到大部分实用算法无法事先预知深度,只能靠具体实现的安全特性擦屁股,显然正常的工程上完全不靠谱的。

这种语言特性设计的限制是人为的瑕疵(artifacts) ——根源之一即是出于对递归的无知。对C和C++来讲,动机主要是使用体系结构的调用栈,而实现这类特性的ISA设计人员在这方面的理解水平普遍处于20世纪70年代(Scheme刚刚提出一等续延的典型形式之前)的水准,其他人却因为兼容性问题而不得不向愚蠢的设计低头,并且会坑到自己人(例如Intel面对自家产品至少有过两次类似的失败)。对Python这样的非native语言来讲,则是设计者知识水平的无能和面对现实问题的一厢情愿——只是不至于连深度多少都无法预知(保证栈溢出能报错)而能让用户有机会积极地试错,可移植性上的实际效果稍微好那么一点点而已。

C++的情况比C稍微复杂一点儿。和Racket的contract类似,C++的非平凡析构函数可以有副作用,所以允许典型的PTC会引起某些程序的可观察行为的改变,这只有ISO C++能协调修改语言规则做出对as-if rules的让步(一如copy elision和allocation merging)。除去这点,具体实现倒是能提供更强的保证;至少GNU C++使用-O3是可以用TCO做到部分地PTC的。

所谓语言入门基础说

所谓“语言就是工具”

什么东西适合用 C++ 写?

http://tieba.baidu.com/p/4054226083

@回复 MaDDove :

仍然需要 FAQ ,列个提纲。
什么东西适合用 C++ 写?
现有的参照:http://www.lextrait.com/vincent/implementations.html
为什么?
总体来说理由有两类:
1 不得不用。用其它语言,现实中不够具有可操作性,不是不可能,就是不方便到基本不可能。
有些东西,不管是在什么系统上几乎都只可能用确定的一种或几种主要实现语言。
通常贸然换用其它语言会遇到这种情况:有更大的风险遇到别人都没遇到过,又没法保证自己解决的问题。
如果加了虚拟机之类的间接运行时可能稍微好一点,基本上用其它语言直接扩展也比较麻烦。
这大类理由也适合其它语言,不限 C++ 。
一些大型的应用软件(典型地如浏览器)、应用虚拟机、可复用组件(如游戏引擎)和以及许多特定领域的应用,传统上主要的核心都是建筑在 C++ 上的且现在仍然占据绝对优势(历史上使用其它 C 、 Java 等其它语言实现的很多也迁移回 C++ ),极少有例外。
对于 C ,则集中在操作系统组件(及设备驱动程序)和版本管理控制等相对少数领域内。
还有一些领域,如数据库引擎、桌面环境等,主要实现语言不是 C++ 就是 C ,有更多机会混用。这也是很多情况下统称 C/C++ 的理由。
也有一些领域,曾经 C/C++ 也被相当广泛地使用,但之后使用更简便的其它替代,典型的就是 Web 后端(不含应用服务器自身的实现)。
1.1 历史包袱。很多项目历史代码就是主要用确定的语言写的。
1.2 习惯因素。这影响现有资源的可用性。
2.虽然也可以用其它的写,但因为需求不够固定之类的原因,考虑由此带来的(以及本来就有的)互操作性等方面的风险,综合起来找不到其它更合适的。
这点是 C++ 的专长:高效而同时具有抽象能力,支持多种范式,灵活迁移(当然前提还是用得对)。在面向应用领域的“通用”上可以说是现时(自从 Lisp 被放置 play 以后)最好的——只要你明确了设计,基本上什么都能实现得出来。同时,仍然能够保持一定可移植性。
而且 C++ 的一些设计能帮助避免做出来后发现不满足关键需求还要考虑换语言的情况(和具有 C++ 类似设计目标的,也就只有 Rust 等极少数语言,然而除了 C++ 都不成气候):例如,运行时性能坑爹——基本上用对 C++ 还不能满足性能需求的情况下靠换其它语言也不可能解决问题了。这也从一方面说明为什么很多严肃的应用会迁移回来重新使用 C++ 。
代价是语言和实现都比较复杂,没有足够的训练容易用错;用户素质普遍不够,沟通成本可能很大,导致损失开发效率。但是反过来也说明如果工程手段得当,就比较容易控制在预期范围之内。另外,复杂性同时限制了不遵从可移植要求的滥用(对比 C 很容易发现这点)。
对初学者来说,学习阶段不需要考虑上面的第 1 点原因。因为相当多基础是各个语言通用的,尽管大部分材料都不会清楚地告诉你多少内容,得自己课外补程序语言理论。而只是“会用”,或者会抄代码,难度不会差太多。自己玩玩就更不用考虑第 2 点原因了。所以 C++ 顶用和去学 C++ 没有直接的因果关系。
现实的 C++ 用户,几乎不可能只会用 C++ —— C++ 没有强到让一般用户容易代替其它所有语言的程度。尽管理论上来说可以做得到,但历史包袱和用户水平决定了不现实。于是许多时候都是 C++ + 其它某种“胶水”“脚本”语言。这算是一种妥协。
另一方面, C 的情况类似但更糟,只会用 C 基本上并没有什么卵用——就算是经典的场景你也得会 makefile 之类的至少一种 DSL ,退一步讲也至少别指望用 C 处理文本什么的了。除了这些历史包袱擅长的领域, C 的用途主要就是拿来写抽象解释器(解释器、编译器、汇编器等等)……不过现状是这些领域也正在逐步被 C++ 替代中(虽然比较慢)。
顺便, Scheme 主要就是拿来被实现的……
最后,非得学 C++ 入门的话,找尽量新的语言和实现版本(语用上表面看起来比较像其它“脚本语言”,虽然实现坑得可能会完全莫名其妙),自己反思能解决什么问题。
严格来说,类 C 语言的历史上一些基本的设计(像类型系统)都相当差,有太多随意的 artifacts ,导致很多东西要么没有,要么就是以后不很自然地打补丁糊上去的,背后的理由并不容易看出来也并不容易立刻就说清楚。(所以这里提到 constexpr 、 auto 和 decltype 什么的不理解,我也不方便一一解释。)对天赋没点到设计语言上的用户来说想像为什么要有这样设计的特性和如何去使用都是脱离真正要解决问题太远的负担。所以我强烈不推荐这些语言当第一入门语言——特别提醒, C 没像其它语言那样有那么多补丁(虽然方言特性倒是一堆),但一直只是表面上看起来简单,论坑的密度比 C++ 还大,学了半吊子基本白学还特别容易产生学会了的错觉,影响以后的正常路线。

IDE

http://tieba.baidu.com/p/4170297948

看来这里也要加个TODO。
先列提纲。
XP作死装不了高版本VS以及targetting XP的质量都烂什么就不管了,就说为什么VS不适合新手。
1.编译器是个残废:支持特性缺斤少两,bug一堆。
2.IntelliSense bug更感人,没经验的信了就等着被误导吧。
3.build system也废,没什么现成简单设置改用其它编译器。
4.编辑器还可以,但作为IDE除了上面的残废以外,大部分其它功能学C++压根用不到。
5.最小安装仍然太大,是不是装得成功可能比较看脸。

存储和存储资源管理

TODO conceptual:

  • memory 翻译成“内存”的问题
  • storage v. memory
  • RAM/ROM/...

默认 GC 的危害

归档

(喜闻乐见首个填坑出这个清单的……)

为什么正经的语言设计中(数组等的)索引应从 0 而不是 1 开始

http://tieba.baidu.com/f?ct=335675392&tn=baiduPostBrowser&z=4833521930&sc=99708310397#99708310397

关于索引从0还是从1开始的问题,我现在改变主意了,得加到Q&A套餐里。

会回答这个问题主要是当时正好空明流转的群有讨论过类似的问题,刚批判完某些不好好学数学的。也因此没兴趣重复废话。不过直接来看,确实不那么容易看得懂。——

不好好学数学暴露的并不是一个low问题,虽然数学教育总体上仍然相当low(EWD在那篇文章的最后似乎也暗示了对某些“搞数学的”的不满)。

拐到数学问题上的关键话题:最小的自然数到底是0还是1。

【勿】Pointer 22:12:07
反人类,boost format的编号居然是从1开始的
【不】Leaves 22:16:05
只能说反程序员,人类还是习惯从1开始编号的。
【不】Leaves 22:16:40
刚学编程那时候觉得从0开始编号真是发人类。
【不】Leaves 22:16:44
  
【谓】灰天飞雁 22:17:25
是反C系程序员(? 从Pascal转过来的时候也感觉不习惯
【不】Cheukyin 22:27:56
貌似lua也是1开始?
【管理员】幻の上帝 22:37:41
 
扯蛋,序数和基数本来都是0开始的。除非要扯无穷。
【管理员】幻の上帝 22:38:24
 
你一出生就会从1数数?
一坨停留在幼儿园概念的数学白痴而已。
【言】山旮旯巨侠 22:39:05
火力相当猛
【勿】Pointer 22:39:14
0到底算不算自然数
【管理员】幻の上帝 22:39:31
就是看不惯幼儿园和小学数学瞎教坏小碰友又不管擦屁股。
【言】山旮旯巨侠 22:39:36
我小学的时候算 现在应该被开除了
【管理员】幻の上帝 22:39:42
一直都算。
【管理员】幻の上帝 22:39:51
只有讲数论的时候方便起见才强行不算。
【言】山旮旯巨侠 22:40:03
额
【管理员】幻の上帝 22:40:16
然而一般定义凭什么跟除法开洞扯上关系?
【管理员】幻の上帝 22:40:27
不就是逃避描述不了无限集合?
【管理员】幻の上帝 22:41:00
反正现在国民教育体系下教出来的九成九数学残废,不计较了。
【管理员】幻の上帝 22:43:54
就算数学学傻了没法救了,但是看到怎么表示范围应该也能自己蒙出来普遍情况从1开始有多蠢。
这种常识本应每个人自己发现。即便要偷懒,也有现成的科普:
https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html
【言】学校的黄焖鸡加饮料只要9.9元 22:52:37
6…
【言】学校的黄焖鸡加饮料只要9.9元 22:52:59
帝球涉猎真广…
【言】易均 22:53:50
@Pointer 新的定义中,0是自然数。数学是定义,是工具,不是真理。
【言】江渚流光 22:54:36
帝球到底是如何快速定位到这些文档的...
【管理员】幻の上帝 22:55:01
坑多了看多了有索引。
【言】江渚流光 22:59:05
 
【管理员】幻の上帝 22:59:41
定义自然数的公认基础是皮亚诺公理。
确定自然数基本性质的皮亚诺公理本身确实不关心几是自然数的起始,它只明确有下限。
但是如果不是0为下限,其它用到自然数的地方基本就没卵用,自然数这个概念几乎(除了数学归纳法等极少数应用)就没存在的意义了。(而数学归纳法这类东西的基础也可以直接架空皮亚诺公理单独定义,当然这下基本上只能从0开始了。)
所以基于需求,把0不是自然数就是挺傻逼的。那些以1开始当自然数的定义还得加个whole number来描述包含0的情况,于是“正整数”冗余出来哭晕在厕所。
从0开始也是ISO 31-11钦定的标准做法。
【言】易均 22:59:51
从皮亚诺公理看
【管理员】幻の上帝 23:01:00
皮亚诺公理第一条就可以写死0是自然数或者1是自然数,所以实际上应该是个模板,正式的说法是公理模式。
【言】易均 23:01:08
 
【言】易均 23:01:47
陶喆轩实分析
【管理员】幻の上帝 23:03:04
定义0或者1为基准主要是因为0是加法的单位元,1是乘法的单位元。
然而讲道理皮亚诺公理可不鸟这些代数上的玩意儿。
【管理员】幻の上帝 23:03:12
皮亚诺公理给加法的定义提供基础。
【管理员】幻の上帝 23:03:15
而不是反过来。
【管理员】幻の上帝 23:03:43
光凭皮亚诺公理也没禁止你拿2开始当“自然数”。
只不过没卵用而不够“自然”罢了。
【管理员】幻の上帝 23:04:09
1只有在少数数学分支比较有卵用,而0在几乎所有分支中都有。这就是差距。
【言】Blizzard 23:04:10
 
【言】易均 23:05:54
@	幻の上帝 不同意,皮亚诺公理可没定义加法
【管理员】幻の上帝 23:06:08
当然没有。
【言】易均 23:06:09
只定义后继的意义
【管理员】幻の上帝 23:06:21
但是你定义整数上的加法会借助到皮亚诺公理。
【管理员】幻の上帝 23:06:35
定义实数上的加法还需要其它一些东西。
【言】易均 23:07:02
加法是借用了其中后继这条公理
【管理员】幻の上帝 23:07:14
其实上面说的就是这个意思。
【管理员】幻の上帝 23:07:21
皮亚诺公理才不需要鸟你什么加法。
【管理员】幻の上帝 23:07:46
所以0其实在这个层次上也没有什么特殊的。
【言】易均 23:08:13
陶喆轩这本书好处在于,从自然数到实数定义,环环相扣,国内教材找不到。国内分析教材一来就直接上实数
【管理员】幻の上帝 23:08:19
但是0在其它绝大多数场合下都足够特殊,至少比1要特殊。
所以把0排除出自然数是没事找事。
【管理员】幻の上帝 23:08:38
搞搞布尔巴基啥的吧。
【言】易均 23:08:46
大家可以参考下,蛮有意思,重新建立数学思维体系。
【管理员】幻の上帝 23:08:55
会给你{{}, {{}, {}}}.....}}}}}}啥的么。
【言】易均 23:09:16
布尔巴基?没看过,写什么教材的
【管理员】幻の上帝 23:09:24
也不算重新了。
基本上看这个的也没其它现成的基础。
【管理员】幻の上帝 23:09:51
https://zh.wikipedia.org/wiki/%E5%B0%BC%E5%8F%A4%E6%8B%89%C2%B7%E5%B8%83%E5%B0%94%E5%B7%B4%E5%9F%BA
【言】易均 23:11:06
自然数定义也可以从集合出发,只是这个定义暂时没看过。
【言】易均 23:13:00
这估计就是你说的布尔巴基学派了
【管理员】幻の上帝 23:13:26
自然数定义用λ演算就够了。
【管理员】幻の上帝 23:13:59
在操作的意义上集合论太弱。
【管理员】幻の上帝 23:14:12
而且ZFC还塞了一堆公理模式。
【言】易均 23:14:13
lambda演算只知道丘奇那本
【管理员】幻の上帝 23:14:17
NBG干净点。
【言】易均 23:14:33
他本就是打算重定义数学
【管理员】幻の上帝 23:15:26
https://zh.wikipedia.org/wiki/%E6%96%B0%E6%95%B8%E5%AD%B8
当然传统废柴数学还是挺顽固的。
【言】易均 23:15:34
1910左右吧,希尔伯特打算重定义数学,哥德尔不是证明不行嘛。
【言】山旮旯巨侠 23:15:53
long long直接转成uint64_t处理应该没问题吧
【管理员】幻の上帝 23:15:56
如果新数学能教干净的话,至少不会有什么人对为什么unsigned会wrap之类的问题稀里糊涂。
【管理员】幻の上帝 23:16:12
一般来说没问题。
【管理员】幻の上帝 23:16:26
当然不排除有大于64位的实现,只是我没见过。
【言】易均 23:17:07
看得越多,很多东西没有理所当然的正确性,都是定义。
【言】易均 23:17:25
定义又要符合逻辑
【管理员】幻の上帝 23:17:26
 
除了代数不等式以外,其它几乎全是对码农要紧的部分。
【管理员】幻の上帝 23:17:35
可见传统数学在教育码农上多傻逼。
【言】易均 23:18:21
逻辑又要从这本书入门
【管理员】幻の上帝 23:18:28
 
原因看来只是上梁不正下梁歪。
跟谭×为何如此普及是一个道理。
【言】易均 23:18:44
 
【言】易均 23:18:56
以及三本
【言】易均 23:19:14
 
【言】易均 23:19:36
@	幻の上帝 你是指的哪几门课程?
【管理员】幻の上帝 23:20:01
幼儿园算术、小学数学、初中数学、高中数学。
【管理员】幻の上帝 23:20:14
而且很多有误导性反作用。比如PEMADS。
【管理员】幻の上帝 23:20:32
废物小学数学教育出多少求值顺序都扯不清楚的傻逼。
【管理员】幻の上帝 23:21:06
 
其实讲道理,人教版还是有些新数学残余的。
然而教得太浅,本科还要重来一遍。
【言】易均 23:21:53
我也是最近看本就数学专业的课程,才发现,以前太多坑,也没说清楚。例如经典的0.99999=1的问题
【管理员】幻の上帝 23:22:21
纠结这种问题本应纯属傻逼,但居然有钦定出来也是逗比。
【勿】Pointer 23:22:24
微积分一脸蒙逼学完了,发现也就用泰勒展开写写三角函数实现
【管理员】幻の上帝 23:22:26
明显不知道本世纪数学的进展。
【言】易均 23:22:27
重新看戴德金和柯西定义实数问题
【管理员】幻の上帝 23:22:36
旧数学就是=无误。
【言】山旮旯巨侠 23:22:38
我这边手上一本数学分析~~
【管理员】幻の上帝 23:22:49
更旧的数学和50年代引入的非标准分析来看就不是了。
【管理员】幻の上帝 23:23:02
钦定=的鹦鹉学舌也是够了。
【言】易均 23:23:11
0.999这个其实就是个定义问题
【管理员】幻の上帝 23:23:12
本来这就是数学哲学内容而不是数学内容。
【言】易均 23:23:31
菲赫金哥尔茨的微积分学教程提到了
【管理员】幻の上帝 23:23:48
两种定义都通,只不过历史上钦定=的被先擦屁股干净了,所以就挤进教科书了。
【言】易均 23:24:26
陶喆轩也提到了,本就是有限小数的十进制表示法的一点瑕疵吧。
【管理员】幻の上帝 23:25:04
跟这个无关。
【言】易均 23:25:05
@山旮旯巨侠 你用那个作者的?
【管理员】幻の上帝 23:25:26
你要用其它方法,像p-adic数混搭什么的照样有这个问题。
【言】山旮旯巨侠 23:25:31
大学课本呀~~~ 被我们老师疯狂吐槽
【管理员】幻の上帝 23:25:36
这个问题不是数学该管的。
【言】易均 23:25:56
p-adic什么表示法?
【言】山旮旯巨侠 23:26:25
华东师范大学 数学系
【言】易均 23:27:19
@山旮旯巨侠 看菲赫金哥尔茨吧,尽量国外教材,不过少了测度和勒贝格积分。普林斯顿最近出了4本分析教材,也不错。
【言】山旮旯巨侠 23:28:14
记下了 不过需要我再次拿起课本吧~~
【言】山旮旯巨侠 23:29:21
大学的时候我还怼过裴哩文
【言】易均 23:29:21
陶喆轩看前几章自然数的定义到实数定义就好,其他的,菲赫金哥尔茨这个也是一步扣一步,例题也多。
【管理员】幻の上帝 23:29:26
https://zh.wikipedia.org/wiki/P%E9%80%B2%E6%95%B8
【言】山旮旯巨侠 23:29:26
裴礼文
【言】山旮旯巨侠 23:29:48
可惜没坚持 就跟了前面一部分习题 后面都放弃了
【言】Blizzard 23:30:23
 贵群还能学到数学 有空还是要看看
【言】山旮旯巨侠 23:30:28
吉米诺维奇
【言】山旮旯巨侠 23:30:50
都还给老师啦
【言】易均 23:30:54
国内教材真的懒得说,都是到处抄。表达不清晰,数学本就是逻辑性强的东西,最需要一步一步推导。
【言】易均 23:31:14
习题集北大的也不错

关于契约(contract)

——以及一部分关于传出参数(output parameter) 和指针的讨论内容。待转移。

http://tieba.baidu.com/p/5915123016

八成因为你看的是垃圾代码(即便这是你在教材上看到的主流写法,实际也会避免)。

首先可以给出一个比较普遍(不限定具体语言和用例)但较弱的解释。

这种用法中,使用指针表示所谓的“传出参数”(output parameter) ,保证函数体内的修改会影响主调函数(caller) 所在的外部词法环境(lexical environment) ,作为对缺乏按引用传递参数的变通。这是在C语言中常用的惯用法,而在C++中通常是不必要的,因为C++中直接就有引用类型来应对这种情况。

从程序语言设计的角度讲,使用指针模拟引用参数能简化语言规则,但这里模拟的不到位。使用契约式编程(contracted programming) 的方法分析,最显然地,传引用是不可能有空指针的,而使用指针无法在类型上表达不可空的契约(nonnullable contract) ,因此接口的使用者需要额外的不由类型检查保证的约束。就传出参数的例子讲,使用这个参数的接口的实现(函数体)中应对非预期的空指针就会出问题:是检查呢,还是不检查呢?如果检查,那么接口就具有所谓的wide contract,是一种恶劣的设计(详细暂时不展开);如果不检查,那么凭空多出额外文档约定或者误用出错的成本。这个问题的根本原因是不可空的契约是原始含义,而指针类型不恰当地表示了定义域泛化(允许空值)后的值,是不恰当的类型使用方式,程序的语义和预期接口的语义匹配得明显不够精确。对C这种类型系统功能差强人意的语言,使用附加文档几乎是唯一现实的变通;而使用C++的引用这样的专用类型就没这个问题。在直接有更明显符合原意、可读性更好、更不容易出错的选项下,还要使用抽象能力较差的手法,这至少体现了接口设计者对语言特性缺乏了解。

然后是稍微具体的解释。

为什么使用传出参数?其实完全不必要。传出参数的直接作用是扩散副作用(side effect) ,包括影响到主调函数状态的对象修改操作。而在正常的代码中,这种散播修改的操作是不必要的,几乎总是可以使用返回值和赋值组合解决。不受到具体控制流限制的传出操作本身在工程上经常难以控制容易被滥用,因为补充文档约束基本是得靠人自觉的,不会有语言实现帮你盯着;因此几乎就是烂接口设计的标志。

当然反例也是有的,一是参数本身会影响函数签名而导致修改返回类型会引起其它不便,二是ABI兼容。不过,这两种反例经常被滥用(过度依赖ABI兼容这种历史包袱是在类型选取以外也流毒无穷的更普遍的滥用)。

另外,担心返回值复制开销过大在C++这样的语言中普遍是多余的,因为就语言规则(特别是ISO C++98就有的复制消除(copy ellision)——且不提ISO C++17的强制要求(mandatory copy ellision))来讲并没有这种必须低性能的限制,真发现慢了可以说都是实现质量(QoI) 问题;退一万步讲,即便在主流实现中真慢了(往往是实现迁就ABI兼容的缘故,像Itanium ABI对unique_ptr和指针参数传递是否允许使用寄存器的区别对待),影响又恰好被发现了,也是需要做性能测试和剖析的才能确定的结果。

此外,注意引用不是对象类型(抽象机语义下必须占用空间),而显式约定未指定占用空间的,因此有更大优化余地;即便实际不内联的调用传递参数时主流实现都使用指针兼容的二进制方式,但这在脱离具体ABI约定时没有保证。代替引用反而因为更固定的ABI限制而更难以优化。这根本上也是由于不匹配精确语义(包括“不依赖占用空间”之类的抽象性质)的结果。

第三是就这种建立数据结构的用例的具体解释。

这种惯用法?一个字:烂。

对模块化的接口设计来讲,参数就是应该表示逻辑上的输入,而不是输出(上面说了,用传出参数是不得已的变通,在C++中没有必要)。这种建立数据结构的代码,逻辑上的输入就是调整数据结构性质的参数(完全可以没有),而输出就是建立好的数据类型的实例——通常在C++直接就是某种类类型的数据类型的值。既然不是数组也不是函数类型(作为返回类型会退化为指针——又是个继承自C的烂特性),返回复制初始化能被消除,为什么不通过返回值而非得用传出参数?明显没事找事。

当然,就C++,其实更一般的惯用法是用构造函数(或者更精确地,构造器(constructor) ,因为C++还有构造模板(constructor template) ——不过我这里选择使用程序语言理论意义上的比C++更一般、不限不支持非参数化多态类型(parameteric type) 的“函数”概念的外延来解决这个逻辑矛盾)……这个也就C++开始带的套路(被Java、C#等一窝蜂抄),实际上还是静态类型语言层面上的开洞:理论上构造函数作为函数不需要和普通函数有那么多不同(虽然C++的决定生存期于构造函数调用结束时开始这样的语义规则是必要的)。构造函数的特殊化失败还通过实质的值构造器(value constructor) 在构造函数以外的另一种实现——工厂函数(factory function) 的流行而体现,甚至很久以来就有 std::make_pair 这种标准库设施——因为 std::pair 作为一个模板,其构造函数无法推导参数类型。即便ISO C++17有了构造函数推导建议(deduction guide) ,也解决不了构造函数特有的恶心——如臭名昭著的 EXPLICIT (斜体,自己实现过 ISO C++11 以后的 std::pair 的都该清楚实际实现光是声明就比标准定义字面上长了20倍可能还不够用的问题)……如果有普遍的类型转换限制机制而不是对构造函数开洞(或者进一步干脆从核心语言规则中丢掉静态类型—……emmm,不用想了可能性不存在的),这类问题至少不会那么窘迫。

还是需要强调一下,就是这种接口设计本身在C++是反主流,其实(尤其是关于传出参数的使用)就是在C中也是非主流。(微软的某些API倒确实是一大坨这种玩意儿,因为有的没的ABI包袱和……智商问题,但这已经偏离一般的C口味太大了。况且微软都不会给你这样的实现数据结构的C API。)

想了想,给微软的API设计扣了个帽子,公平起见也说全吧……

微软的C(乃至C++)API中有不少是古董风格的API,也大量使用了传出参数(像COM风格的API就是迁就ABI兼容的典型,至于这种迁就ABI兼容性为什么不是适合工程目标的设计——至少不如.NET的战略——这点不在这里展开批评),使用麻烦怨声载道(特别是去调用的还有很多不习惯微软式C风格的其它高级语言用户)应该不难理解,不过也不是一无是处。微软的这类代码中有很多使用_In_、_Out_这样的保留标识符注释,实际上是称为SAL(安全标注语言)的扩展,这就是一种基于霍尔逻辑(Hoare logic)的契约的实现。只不过这种契约写起来很麻烦,而且宏定义的方式兼容性虽然好却很容易干掉(得靠用户自觉),还是缺乏静态类型检查这样的强制作用,写契约本身工作量又大,效果未必好,可能不怎么值(有点主观的看法)而已。不过,考虑到大多数C和C++用户其实都不吃这套,这个看法也不是纯主观问题了。

最后,所谓什么栈什么内存的都是耍流氓。

实现上面的语义精确性,对用户来讲容易遵循的更普遍的一般形式是最小依赖原则,也就是说根本不需要用底层实现的特例形式来解释的东西就完全不必要。像栈分配多少大小给参数,撑死也就是指针传递实现按引用传递参数的一类ABI的描述,这种以偏概全的手法连C++的抽象都表达不出来,尤其有害的是容易对举一反三不习惯的新手构成误导。而就抽象而言,C++这里显然还嫩呢……

就这个新手(但很多写教材都不得要领的)问题我还要提两点展开内容。

一个是比较直接的问题:不提倡使用节点类型表示数据结构。

最直接的原因:数据结构的节点和表示数据结构的抽象数据类型就是两回事。在C++这样的使用名义类型系统(nominal type system(en-US)) 来提供类型抽象的语言中,使用不恰当的类型几乎就是“错”的——至少给出了类型抽象上的误导。

更普遍的一点:抽象数据结构和类型本体论意义上的直接(尽管未必一一)对应是显然的,也有大量数学模型(类型论)上的支持,如果不是为了具现(reifying) 突破语言特性的限制(像C++缺乏first-class continuation,也缺乏足够“高级”的控制关键字,要可移植地模拟必须使用不那么直接的变通的表示——要是允许使用特定体系结构的汇编之类不可移植的实现方式就能相对容易地使用如WG21 P0534R1这样清楚的表示方式)完全不应该这样变通(是没事找事)。这里不再展开。

第二个,关于上面说的wide contract。这是个实际上应该是基础但教育上普遍缺失的问题,反倒是在C++因为语言设计的问题反而得到了重视,无力吐槽…… 关于narrow/wide contract的入门参见WG21 N3248。这在库的设计上有深远的影响:一个主要的例子是WG21 N3279;再如用于反对WG21 P0903R1

C++2a还在计划搞contract支持(一大坨paper),不过我认为更像样的方向应该是(Typed) Racket。静态语言混淆“尽早”和“静态”地约束契约(被静态语言类型系统弱化为所谓的“类型检查”)在通用目的语言设计的意义上是没有出路的。

什么是接口(interface)

http://tieba.baidu.com/p/5904366799

先学Java的恶果的活教材……

接口(interface)的一般含义本来就不是Java教你的那坨玩意儿。

Java的所谓interface用法上相当于C++的所有非静态成员函数都是纯虚函数的抽象类。对C++而言,所谓的接口继承就是一般继承的特例。因为Java设计上不允许多(实现)继承,要混入(mixin)实现是不可能的,所以只能依赖阉割版本的类。

这种所谓的接口只是一种用特定的类型表示真正的、一般含义的接口的方法——注意这里的概念和Java无关。所谓的“接口”和“方法”这样普遍的概念被Java术语一扯以后怕是话都让人说不利索了,这是我反对先学Java入门的主要理由(之一)。

一般的意义上的接口有以下几类:

硬件意义上的电气接口:连接器(插头、插座)、数字音频转换接口、网络接口(所谓网卡正式名称就叫“网络接口卡”); 开发者使用的可编程接口,包括应用程序编程接口(API, Application Programing Interface) 、应用程序二进制接口(ABI, Application Binary Interface) 、以及面向对象所谓的接口(也就是上面提到的如 Java 、 C# 特性这样的特例)等等; 用户接口(UI),interface一般翻译成“界面”,如图形用户界面(GUI) 、命令行界面(CLI) 。

后两者之间严格意义上来讲界限并不明显。系统管理员使用的 CLI 中往往具有足够的可编程性,例如通过使用脚本的形式管理系统资源。操作系统提供的一些抽象中也有一些兼具以上两者的特性,如对应硬件接口功能的设备或虚拟的同类设备。此外,使用硬件描述语言(HDL, Hardware Description Language) 等方式可以直接描述硬件接口,尽管自身属于软件范畴。

题外话:

  • 顺便,我也不赞成使用任何内建名为“模块”(module)特性的语言入门——和“接口”一样,“模块”也是被曲解的典型概念。
  • 当然,C++2a还有叫做“概念”(concept)的特性嘛……
  • API 也是被曲解的常见概念之一。和接口类似,形式是因为先接触具体实例而以偏概全。因为莫名其妙的历史原因,不少人接触的 API 是 Windows SDK 的所谓 WinAPI (宏 WINAPI 意义上的 API ),实际上只是 Win32 API ( SDK 提供相关的头文件和一部分库,大部分动态库随 Windows 的安装分发),都没法涵盖 Windows 的其它 API (如 NT 内核及 DDK 提供的 NT native API 和其它非公开 API )。实际上, Windows SDK 一直不只提供 Win32 API ,还至少提供 CRT(C runtime) ——包含 ISO C 标准库 API 以及实现;现在还有开发所谓 modern 应用(包括 Windows 商店应用)的“通用”(universal) API 。
  • 不赞成使用 Java 入门的其它主要理由至少包括对语言设计的误导以及对工业需求的曲解。这些理由实际通常对别人的影响更大,而学习者自以为得计。

更一般地,接口是具有明确目的被代码作者提供的一种契约(contract) 形式。这个意义上,Haskell 的 type class 、 C++ 的 concept 和 Racket 的 contract 等等都算近似的“接口”特性(即便因为过于抽象仍然没有涵盖完全)。

某些语言特性的构造

http://tieba.baidu.com/p/5476456843

C++这样静态类型的静态语言提供的所谓traits一般可以理解成基于合式(well-formed)的构造,对应典型实现中符号表中项的存在性,以及基于这些性质的中能保证在翻译阶段(phase of translaton)即能给出确定结果的函数(所谓“元函数(metafunction)”)。

用Lisp的术语来讲,符号表实现了一个求值环境(evaluation environment)。几乎所有的语言(包括通常意义上的Lisp)不会暴露一等环境(first-class environment)作为语言特性,而静态语言经常根本不会明确存在这样的抽象(反正告诉你你也没法自己改里面的内容,否则就不静态了)。提供一等环境的语言(如Kernel)可以允许用户以一致的情况下同时提供不同阶段(phase)的抽象,因此不需要宏(macro)这种单独阶段变换就能实现更一般的功能,同时不增加针对特定阶段的特别规则。退一步,单独提供卫生宏(hygienc macro)是Scheme等语言的做法,这种做法要求分离阶段,但仍然允许保持语法上和语言中的其它形式表示一致。相比之下,ISO C和ISO C++规则在这里就复杂得多,逻辑上都要求分为不少于7个翻译阶段(phase of translation)——这还不包括后面的运行;其中,宏展开在第4阶段——还有不受作用域规则限制的污染(所以不卫生)和相当大的和后续阶段语法不一致的特别规则。

C++2a提议的metaclass是不完全保留一致的比卫生宏更弱的设计。而模板则是另外几种限定了翻译阶段而且和静态类型系统有不止一腿混乱关系的特性的总称。这几类特性可以实现部分用卫生宏的设计,但甚至还没有能力在实用时完全取代原有的宏,而复杂性谁用谁知道(虽然一部分来自于静态类型系统必然的无能,以及C++静态类型兼容历史包袱的特别的无能,但特性本身的规则冗余也是重要原因),所以相当地鸡肋。

至于类和继承之类的琐碎高层特性,在已知实现使用的ABI和对布局进行抽象的特性之后,不值一提——只要基本的特性够用,总是相当容易实现。限定支持或禁止多继承除了提供维持可移植的样例外,对足够通用的语言几乎毫无意义;只不过这些语言的抽象能力和表达能力都太弱,无法随便让用户在语言中利用组合现有的基本特性添加这类特性而已。

在表达能力足够强的语言中,用户可以自行实现语义规则,包括对各个阶段的类型编码成特定的数据结构并添加类型检查(因此静态类型总是必然具有缺陷——只要用户能发现现有的内建静态类型系统不够好用而又不得不克服缺陷的时候——在C++这样历史包袱严重的语言中尤其容易遇到)。在静态类型语言中,类可以作为一种静态类型,基于记录类型(record type)(如C那样的结构体类型)或更一般积类型(product type)(如元组(tuple))这样类型理论中特定基类基础组合类型的特例进行抽象。(也有直接设计使用类的类型代数,但太复杂。)所谓的抽象类是特殊的包含未完全实现的函数作为其方法(“抽象方法”)的类。所有方法都是抽象方法的类即所谓的接口。而“继承”是类之间的元函数:输入超过一个不同的类作为基类,派生出一个具有特定元数据(成员、布局乃至隐含的二进制符号等等)类作为结果。 多继承在这里仅仅是明确允许输入的类能超过两个而已,没什么特别的含义。

C++用的是这种构造方式来提供类类型。一些其它的语言设计为了支持这类所谓的面向对象编程就直接支持了所谓接口等本质上更加复杂的特殊特性,实际上只是掩盖了语言设计抽象能力不足而已。有的语言设计者偏执地认为超过一个基类的设计不够面向对象或者会经常造成实现困难而直接越俎代庖替用户决定不支持多继承,乃至连混入(mixin)这种基础用法都要搞成什么模式,对此只是更加容易让有脑子的用户呵呵而已。

语言设计问题相关实例

基础知识

文本编码

一般语言话题

标准库的称呼

求值策略相关

数组和指针

数组和指针(误导实例)

指针和引用

安全性

C 范围检查

http://tieba.baidu.com/p/4389930794

线程安全

http://tieba.baidu.com/p/5683849936

一看就属于没经验的…… 靠谱点讲好了,这种允许副作用又允许共享可变状态的语言,给你都保证线程安全才是真的烂……跟UI没什么关系,只不过搞UI的特别习惯在这里被坑而已。 异常?给你异常就不错了。数据竞争就是UB,鬼知道会有个毛线。

一样的道理,想想为什么野指针跑飞了会挂?给你保证不会跑飞有什么代价?能自觉保证正常的代码的码农凭什么要陪写烂代码付出这种代价?要可移植还没法没有代价咯? 唯一要求确定行为合理的,是对付不可信代码,不能放任不可预知的行为。而划分什么代码可信本应由用户决定,用户决定不了就代理给码农决定,而不是语言的设计者自己替用户脑补,否则就是一种无能——不允许可移植地避免显然的代价。Java在userland的实现其实已经就混淆了这种问题,要求NullPointerException至少是两种蠢(一种是不懂利用静态类型系统避免大部分可能引起这种问题的情形,另一种是不懂Hoare logic这类简单的工具,没脑子区分出narrow contract);当然,某些人就是喜欢把兼容蠢代码当作“安全”,只能呵呵。

delete 后清空指针值

http://tieba.baidu.com/p/4325323468

看来还是有必要上FAQ。提纲。
0.确定在这之后必须使用这个空值是另一回事。然而具体到指针上如果不是内部实现,一般也是蠢用法。(智能指针/optional死哪去了?)
1.不管是不是空,反正访问了都UB,语言层面上一样没保证(扯什么异常的妥妥重修)。
2.考虑具体实现,基本上也就是调试,所谓的防御性编程除了糊弄(隐藏错误行为,而不是错误的来源)外没卵用。往往因为你不知道的——并且这种做法下通常你更难以搞清楚的——原因,运气不好一点QA都糊弄不过去(特别是你只能测试你自己撸的有限的部分,而UB炸了原则上是没有范围限制的)。
3.给自己没事找事:破坏第一现场给调试造成麻烦。
4.仍然是没事找事:废话+语义噪音。
上面是鸡汤式偷懒思路。如要考虑原则正确性问题,以上几点从下往上看。

分配函数

http://tieba.baidu.com/p/4303278739

算了,这个也是经了,加个work item列入FAQ。这里列提纲。
首先,如果是你自己想出来的问题也是罢了,如果是别人教的……拖过来我保证不打死他。
什么new/delete和malloc/free?抱歉,没个卵关系。
非得沾亲带故,也就是new和delete不和operator连用能有new-expreesion和delete-expression的语法,然后new-expression和operator new有一腿,delete-expression和operator delete有一腿,然后调用默认nothrow版本的::operator new的行为可以类似调用malloc,调用默认::operator delete的行为可以类似调用free而已。原来的问题仍然是蠢问题。
不算placement new以外的new-expression和operator new调用的求值主要差别是前者可以多出来构造函数调用;delete-expression和operator delete调用的求值主要差别是前者可以多出来析构函数调用。而placement new某种意义上就是手动调用构造函数……

operator new/operator delete和malloc/free差别一箩筐,即便行为能够接近:
前者是钦定的allocation/deallocation function,后者充其量只能是可能的实现;
前者能在类作用域重载后被自动用上,后者没这待遇;
前者是区分数组和非数组版本的;

除非nothrow版本,operator new失败抛异常,而malloc失败返回空指针值;
前者的全局版本可以由用户替换标准库实现提供的定义,没有任何其它函数有这种待遇;

异常了滚回去的时候operator delete会被自动调用。

说白了差得太多太明显了,malloc/free充其量只是和nothrow版本的::operator new/::operator delete在行为上能有那么点可比性而已。混一起炒冷饭的是什么居心,嫌不够乱?

在理解上面的常识之前,记住不管是哪坨玩意都不是给你用的,随便乱用八成效果是通不过code review找抽或者蠢代码之一。

再补三点不限LZ估计也不太容易注意而不是那么像常识的,想无聊撵人的可以塞面试题或者业务考试里:
1.数组版本的operator new在ISO C中并没有对应。注意calloc是带初始化填0的,目的不一样;
2.malloc对0参数的行为impl-def:
WG14/N1570
7.22.3/1 ... If the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.
而allocation function在此情况下可能失败但不是impl-def,没有要求实现文档明确,而返回的结果上附加约束:
WG21/N4567
3.7.4.1/2 ... The effect of indirecting through a pointer returned as a request for zero size is undefined.36
36) The intent is to have operator new() implementable by calling std::malloc() or std::calloc(), so the rules are substantially the same. C++ differs from C in requiring a zero request to return a non-null pointer.
3.C++14开始实现有权合并满足特定条件的::operator new调用而不需根据as-if rule证明不存在可观察行为改变后才能优化。C没有类似的东西。
WG21/N4567

5.3.4
10 An implementation is allowed to omit a call to a replaceable global allocation function (18.6.1.1, 18.6.1.2).
When it does so, the storage is instead provided by the implementation or provided by extending the
allocation of another new-expression. The implementation may extend the allocation of a new-expression e1
to provide storage for a new-expression e2 if the following would be true were the allocation not extended:
(10.1) — the evaluation of e1 is sequenced before the evaluation of e2, and
(10.2) — e2 is evaluated whenever e1 obtains storage, and
(10.3) — both e1 and e2 invoke the same replaceable global allocation function, and
(10.4) — if the allocation function invoked by e1 and e2 is throwing, any exceptions thrown in the evaluation
of either e1 or e2 would be first caught in the same handler, and
(10.5) — the pointer values produced by e1 and e2 are operands to evaluated delete-expressions, and
(10.6) — the evaluation of e2 is sequenced before the evaluation of the delete-expression whose operand is the
pointer value produced by e1.
[ Example:
void mergeable(int x) {
// These allocations are safe for merging:
std::unique_ptr<char[]> a{new (std::nothrow) char[8]};
std::unique_ptr<char[]> b{new (std::nothrow) char[8]};
std::unique_ptr<char[]> c{new (std::nothrow) char[x]};
g(a.get(), b.get(), c.get());
}
void unmergeable(int x) {
std::unique_ptr<char[]> a{new char[8]};
try {
// Merging this allocation would change its catch handler.
std::unique_ptr<char[]> b{new char[x]};
} catch (const std::bad_alloc& e) {
std::cerr << "Allocation failed: " << e.what() << std::endl;
throw;
}
}
—end example ]
(嘛,这里的例子被我在working draft里改过。。。)

<iostream><cstdio> 有什么区别?

http://tieba.baidu.com/p/3971013816

1.cstdio是面向“文件”的,或者不强调文件和非文件流的区别,默认流就是可以关联外部文件,至于文件的外延是啥就不管,扔给宿主环境了。从std::FILE这个名字以及printf/scanf接口描述基于fprintf/fscanf上就可以看出来。
iostream头只是包含了一坨东西,封装标准输入输出流,和文件流(在<fstream>)不通用。
2.cstdio不知道iostream,而iostream知道cstdio并且默认同步,此外提供有限的接口摆脱关系(sync_with_stdio)。因为这个接口约束,iostream要脱离cstdio(的实现)是不太可能的。
3.cstdio有orientation的概念;iostream是否wide是直接写死在静态类型的模板参数里的,并且底层的流不只支持char和wchar_t字符类型。
4.iostream底层公开了相对完整的缓冲接口(如std::basic_filebuf),可以自行扩展;cstdio只能笼统地设置缓冲模式和提供提供区的接口,但多了行缓冲的概念(_IOLBF)。
5.iostream和std::locale交互。C标准库的<locale.h>(对应C++的<clocale>)提供locale-specific behavior,和cstdio不直接关联。两者不是一回事。
因为这个原因,iostream能直接编解码字符串,而cstdio不能处理和wide-oriented无关的编码,需要<wchar.h>(对应C++的<cwchar>)或C11的<uchar.h>的例程显式调用。
6.iostream体系提供了基于字符串提供了“内存流”(主要是std::stringstream),而cstdio这部分一般只是内部实现,如果需要得自己造。
7.iostream底层通过继承和重写protected虚函数提供实现。cstdio这部分是内部实现,不提供与之对应的扩展功能。
8.两者都实现了流的状态,但不尽相同。iostream显式区分bad和fail,但cstdio没有。
9.iostream提供特定的打开模式的组合,而cstdio使用字符串参数。前者无法直接扩展;后者解析较低效,虽然不需要修改类型就可扩展但也存在实现的运行时兼容性问题。
10.除了检查流的状态,iostream处理错误可选使用异常。cstdio处理错误依赖返回值和流状态。
11.iostream的格式输入输出基于重载,静态分派且类型安全,可以实现得更高效;cstdio的格式输入输出使用的是领域特定语言,需要运行时解析,通常比较低效,且实现的运行时兼容问题修复较困难。
但后者同时提供运行时配置可修改的格式的功能,而前者没有。

另外, iostream 在主流实现默认比 cstdio 慢的主要原因,基本上就是因为兼容后者的锅。具体来说,后者为了兼容某些扩展行为,通常会实现为具有互斥锁定语义的底层调用。默认前者允许和后者混用,结果保持同步,实际实现一般也直接复用后者的部分接口和实现。这要求每次 I/O (极端情况下,每个字符的 I/O )进行一次锁定,在典型操作系统下很可能涉及陷入内核的系统调用,上下文切换的单次开销即可能无法忽视,频繁调用导致性能极端低下。

解决方案:一个 sync_with_stdio(false) 天下太平。注意要在开始使用前确定调用,确保之后不使用 cstdio ,否则无法确保 I/O 行为可预测。关闭同步后,实现可使用对应非同步的接口(如 getc_unlocked 代替 getc ),不存在此类性能瓶颈。

关于 scanf_s 等接口

由微软在 Visual C++ 中提供,后来提交标准化为 ISO/IEC TR 24731-1

这些接口的实用性存在争议反对者包括 Austin Group ( POSIX.1 起草者)以及 Ulrich Drepper ( GLibC 维护者)等

虽然后来这些内容作为 Annex K(normative) 进入了 ISO C11 , 但并不要求所有实现支持(明确只有定义了宏 __STDC_LIB_EXT1__ 的实现需要支持)。计划引用 ISO C11 的新版 ISO C++ 要求实现定义。而当前仍引用 ISO C99 的 POSIX.1 并中不包含对此的实现要求。

正因为当前由于绝大多数主流实现者拒绝实现而导致有 WG14 提案提议把它从未来的语言标准中移除或先 deprecated 后移除,使用它一直会导致可移植性问题。

<cstring><string>

http://tieba.baidu.com/p/4641914364

完全不同的标准库头。

按 ISO C++ 的惯例,前者对应 ISO C 的 <string.h> ,区别 <cstring> 要求库函数在 namespace std 声明并且还多出来一些 C++ 支持的重载(对 <cstring> 而言主要和函数参数和返回类型中的 const 有关),以及 ISO C++ 要求的库函数保证不以宏实现。注意 <cstring> 不要求包含 <string.h> ,所以全局命名空间可能有或者没有对应的 ISO C 函数的声明,而 <string.h> 可能支持的非 ISO C 扩展(如 POSIX 的 strdup )也不能保证在全局命名空间中声明。

<string> 主要提供 std::char_traitsstd::basic_string ,和 <string.h><cstring> 提供的接口无关。

另见下一节注意事项。

关于 C 和 C++ 标准库头

注意 ISO C++ 包含了 ISO C 的标准库头,但在 ISO C++ 中被 deprecated ,如无必要,使用 C++ 标准库时, <xxx.h> 应尽量用被 <cxxx> 代替。必要情况如前言使用 POSIX 扩展。尽管甚至 Stephan T. Lavavej 都曾表示不知道这为何被 deprecated ,这里的主要原因应是鼓励这种做法。

顺带,注意到某些人鼓吹只使用 <xxx.h>,如:

另外 ctime 头文件没定义 std::gmtime_r,而 time.h 定义了 ::gmtime_r。我可不想去背哪些函数是 C 语言的哪些是 Posix 的,哪些头文件是 C 语言的哪些是 Posix 的(在Linux下,二者基本不分家)。为了用几个系统函数(例如 fcntl() ),我该 include cfcntl 还是 fcntl.h?用线程是 cpthread 还是 pthread.h?我总是记不住 memset() 的参数顺序,因此一般用 bzero() 代替,但是 manpage 说 bzero() 声明于 strings.h,那我要不要考虑试试 cstrings 呢?何必给自己找麻烦,C++ 标准库之外的内容干脆统一用 .h 头文件好了。

硬伤一堆,列举如下:

  • “ctime 头文件”——暴露出不知道ISO C++ 标准库头不要求实现为“文件”
  • “C++ 标准库之外的内容干脆统一用 .h 头文件好了”——没有搞清 C++ 标准库的外延。不管 <xxx.h> 还是对应的 <cxxx> ,当前都是 C++ 标准库要求实现的一部分。并且如上面 STL 所说,看样子前者并不打算短期内移除。
  • “哪些头文件是 C 语言的哪些是 Posix 的”—— POSIX 头文件要求支持 C (因为 POSIX.1 直接就是 ISO C 的扩展),可不保证支持其它语言;然而实现中,很多都有 #ifdef __cplusplus 这样的考虑,所以区分什么头文件是什么“语言的”本来就是莫名其妙的做法。应要知道的,是指定的接口来自于哪个规范,以便减少必要时查证的开销
  • “系统函数(例如 fcntl() )”——概念混乱。系统函数是指什么?系统调用?用户空间提供的 POSIX 的接口的实现?
  • “我总是记不住 memset() 的参数顺序,因此一般用 bzero() 代替,但是 manpage 说 bzero() 声明于 strings.h”——愚蠢。 BSD 发明 bzero 并不是为了纵容记性差,记不住为什么不查?(哦,好吧,会 man bzero 是吧……怎么不会 man memset 了?)连这点都做不到,写的代码谁敢用?更别提 bzero 本身可移植性明确更差的事实了。
  • “那我要不要考虑试试 cstrings 呢?”——文档明确告诉干嘛了还多此一举,吃饱了撑的?只有找文档的漏洞时这样做可以理解,不过看上面连搞清楚引用的哪个文档都懒得做,实在不像是有做这个的资质。

这样做纯粹损害可移植性而没有获得任何好处(允许以混淆读者视线而适合让自己变蠢这点在此论外),应予视为误导。

标准库头包含

经常有见到什么某个标准库头(如 <iostream> )包含另一个(如 <cstdlib> )所以不用包含后一个就能用,不信看源码云云——明摆着犯了特定实现代替接口的以偏概全的逻辑谬误。

实际上, ISO C++ 还真规定了一些必须(等价于)包含的情况,但也就只有那么些情况

32 位 v. 64 位

http://tieba.baidu.com/p/4816012657

什么是64位CPU 要了解什么是64位的CPU,先要了解以下几个概念: CPU的位和字长 位 在数字电路和电脑技术中采用二进制,代码只有“0”和“1”,其中无论是“0”或是“1”在CPU中都是一“位”。

数字电路不一定需要使用二进制、 ENIAC 之类的古董没用二进制以及没有CPU之类的低级常识硬伤不提,首先你缺乏指令集架构(Instruction Set Architecture, ISA) 的概念。

CPU 设计从来不直接和什么位和字长关联。“字”在一定意义上纯粹只是历史遗留物。“字长”这种概念也是冗余的。

能套用这种概念的是操作的局部特征。特别是 IA-32 这样的历史包袱严重的一大坨玩意儿,即便是寄存器,位宽不都一定。

所谓64位CPU,严格意义上只是说能实现 64 位 ISA 的一类设备。而64位的ISA也仅仅只是指其中的一些典型操作使用64位,并不是全部。一概而论是没有意义的。

对IA32兼容架构来说,64位体现在特定系统模式下(通用目的)指令使用的地址的默认位宽。具体点说,首先需要进入IA-32e模式(从保护模式开始,关闭分页、启用物理地址扩展(PAE) 、加载 4 级页映射表(PML4) 、设置 IA32_EFER.LME = 1 、启用分页),然后在 IA-32e (IA32_EFER.LMA = 1) 下:

1.64位模式:代码段描述符满足 CS.L = 1 和 CS.D = 0 。此时默认操作数32位,默认地址64位。使用指令前缀可以改变操作数大小为64位和16位,地址大小为32位。

2.兼容模式:代码段描述符满足 CS.L = 0 。此时可以运行16位和32位的程序,绝大多数架构行为和保护模式一样,但线性地址到物理地址翻译、中断和异常处理以及系统调用(调用门和SYSENTER/SYSEXIT)使用IA-32e机制。

其它64位ISA当然不需要有一致的设计。

字节和字长的区别 由于常用的英文字符用8位二进制数就可以表示,所以通常就将8位称为一个字节。字节的长度是固定的,而字长的长度是不固定的,对于不同的CPU,字长的长度也不一样。

这个是典型扯蛋。

在一定意义上字节(byte) 也是历史包袱。历史上 1 字节一开始就不确定几位。像 TAOCP 设计的 MIX 就是 1 字节 = 6 位。而 ISO C 和 ISO C++ 明确支持 CHAR_BIT >= 8

要求表示8个二进制位数据的场合(如某些 RFC)使用的术语是八元组(octet) 以刻意规避字节概念的不精确。

只有微处理器流行后实施的设计如 POSIX 和 Java 之流才会假定 1 字节正好 8 位,但这也削弱了 ISA 层次上的可移植性。一些 DSP 使用 16 位为 1 个字节。也有干脆任何数据都是 64 位的 ISA ,对这种 ISA 来说区分字和字节都没什么实际意义了。

字长 电脑技术中对CPU在单位时间内(同一时间)能一次处理的二进制数的位数叫字长。所以能处理字长为8位数据的CPU通常就叫8位的CPU。同理32位的CPU就能在单位时间内处理字长为32位的二进制数据。

什么叫“一次”?一个时钟周期?不巧,现在的主流 CPU 都是流水线化 + 多发射的,你根本说不清楚什么叫“一次”。

8位的CPU一次只能处理一个字节,而32位的CPU一次就能处理4个字节,同理字长为64位的CPU一次可以处理8个字节。 这样AMD的64位的CPU在处理速度上较32位的CPU理论上快了一倍。 8位处理器、16位处理器、32位处理器和64位处理器,其计数都是8的倍数。

扯蛋不重复纠正,不过还要指出存在 36 位等非 8 为倍数位数字长的ISA,即便现在都是古董。

剩下的我就粘了,很麻烦,另外我已经强调特指处理器性能,而且是理论值,我不知道你是什么意思?x86架构方面的专家?还是什么?为什么你就这么牛逼?即使我说的不对,用不着这样吧?

理论值?什么理论?连 Amdahl 定律这种层次都不到的YY?

没分辨能力粘个蛋蛋,全是胡扯。虽然手打科普是我的事,但是就因为有这样胡扯乱粘的才增加我科普和挂人工作量。要点脸行不?

为何我需要纠正你的笑话?不巧,我现在就拿这个吃饭。我这里造 CPU 特么的一坨恶心的破事就被你两句话轻描淡写打发了?

另外我在申明一点,我所谓说的如今不存在谁快的问题,是因为如今的硬件水平,起步已经在4g内存之上了,所以不存在楼主所说因为内存不足而造成的性能下降,我不知道你怎么理解的,啰嗦半天,装的跟大神一样。

这贴里没说就是什么硬件吧?这个吧的主题能适用的硬件也不管你这个假定吧?比如说,这里谁的手机都是 4G 以上了?我的特么就不是。

另外,我最后再说一点,如果内存过下,确实会出现楼主所说的因为64位相交32内存占用略多而造成性能下降问题,我之所以说实际使用不存在这种问题是是因为除了上述原因,还有最重要的,64bit最低内存要求为2GB,2g并不会出现性能降低的问题,即使有,那也是windows

神特么甩锅给 Windows 。除了地址空间划分的问题,对应用来说,是不是 Windows 有什么区别?哦,是有一些 ABI 问题,但是你会管什么 FS 什么 GS 不?

GUI 入门

图形 v. 图形用户界面

TBD

常见误区

http://tieba.baidu.com/p/4381930595

1.目的。

如果是上班,要你撸什么用什么,当然基本上靠视频都是低效做法。如果是瞎忽悠要你调研自己不清楚的东西还负责选型,出问题谁背?这样的单位趁早跑路。

如果是自己撸……没事撸毛 GUI ?还用 C艹 ?

技术选型能力比起语言使用能力在这里很可能更重要。

2.所谓GUI。

三流科班和野路子出身的往往喜欢望文生义 GUI = G + UI = G + U + I,结果把经典 GUI 成功的部分如 WIMP metaphor 全给扔了。不是说不能创新(比如说从桌面迁移当然应该有变化),但是一问三不知,是罪。

3.抽象层次。

这里着重需要婊的是所谓“立即模式”GUI(IMGUI)。说白了就是早年性能等限制导致的一个实现泄漏出来的抽象,近些年居然好意思开宗立派了……

对付这类玩意儿,一概同没有 AST 的辣鸡编译器一样论处。

(另一个要婊的是 FRP 和 UI 相关的倾向,但这个也坑着。)

这类玩意儿,倒不是说学了完全没用,一些时候也就是需要这样解决实际问题。但完全没基础的学这个就是毁三观的歪门邪道。

4.依赖。

首先,没法划清边界,无原则依赖具体实现的所有软件都是半吊子。 GUI 也不例外。需要拎出来说是因为历史原因造成了GUI不方便跨平台的错觉,而实际上只是因为有兴趣和耐心搞 GUI 框架的,大多对正常姿势的软件工程方法和码农手段一窍不通而已,自然上梁不正下梁歪。

这里需要着重婊的是凡学 GUI 先讲 Win32 的 Win32 中心主义(即便没有 Win32 外的跨平台需求)。实际上Win32的实现从ReactOS等途径扒出来就可以看到极其狼狈,而接口抽象设计上也是非常拙计。应用层实现不算 C 的辣鸡风格 API 的问题还有一大坨破事,比如 WndProc 加个 void* 会死?——这类烧饼问题基本上现代的其它解决方案都不会有。强行 Win32 能和其它方案同等质量地(几乎不可能,至少考虑成本是这样)完成项目的确可以算是炫技,但另一方面在实现出来前,光是选型就是炫蠢了。

(比 Win32 更烂的其实不少,主要是非 Windows 平台上流行,略过。)

MFC 为什么 zz ?主要原因也是类似,真正干活的几乎全是外包给Win32的。所以就学习而言MFC还不如Win32,即便项目做起来代码看起来没那么屎(但这也仅相对 Win32 而言)。架构嘛, Document-View 的过气笑话也不值得浪费时间。至于CArray被吊打几条街之类的玩意儿就不提了,反正设计 MFC 的在非 GUI 的问题上往往更加外行。

所谓的 DUI(Direct UI) 是另一个典型反面教材的例子。 DUI 原本只是微软的某个实现中发明的架空 HWND(Win32窗口句柄)的技巧,说白了是一种 hack ,结果莫名其妙被没见过世面的井底之蛙搬过来变成了架构创新层面的大煋闻。实际上,如果真按所谓 DUI 的精神,世界上的 GUI 从来都是 DUI 的,只不过实现了 Win32 (其实是 Win16 )的 GUI 之后 HWND 成了 DUI 的一个实现极烂的代理。会有这种错觉,主要原因恐怕就是上面所说的 Win32 中心主义,以及本身学习能力、眼界、想象力和品味太差,空有生产烂轮子的山寨水平这个原因了。

至于BCB这样(某种意义上包括整个所谓的RAD行业)的过气玩意儿,不予多评论(现在叫BCB的和VC6一样古董我就不多戳穿了)。就说一点:鼓吹BCB好转进C#?那为什么不直接学C#?

用户体验和产品评论

为什么黑苹果

https://tieba.baidu.com/p/5151307647?pid=108091070649&cid=0#108091070649

为什么黑苹果?指鹿为马、不公平竞争、混淆视听、误导消费者……这个自己看着办,总之不只是莫须有。

这里就澄清对动态加载相关实现(包括这里的热更新)在技术方面的一些实质。

就@lcc771209 的理解的所谓的(指令)代码和数据的问题来讲,至少在软件实现的角度上,这两者没有严格的界限,不需要一定被区分。本来代码说白了就是编码了的数据的一种。

业务上需要怎么区分是另一回事,取决于具体业务的需要。不同软件对待这个问题的答案不需要一致。回答什么东西算所谓的“纯数据”,还轮不到应用商店以审核政策一刀切。

一般地,这里可以有几个不同的角度的理解。

1.软件层面上,被误认为有必要区分指令和数据才能实现一些功能或保证某些性质,不过是事先站在体系结构和宿主环境(操作系统)下所谓本机(native)语言的实现这种“系统软件”的角度上看问题不腰疼罢了。这类语言一般不是所谓的动态语言,正因为它们预先假设了可执行的部分和不可执行的部分。多数开发者在很长一段时间之后才会意识到,这种预先假设只不过一种语言设计上的偷懒和甩锅而已,对开发应用这个目的来讲并不是必须的。反过来,强制要求遵守这个假定可能会显著影响开发效率(因为这些语言多数设计得抽象水平非常拙计不好用),于是业界才会出现性能拙计的动态语言实现更流行的情况。(性能太烂坑到最终用户是个问题,但不是在此讨论的主题;合理的使用反而有利于性能;后面还会看到强制策略对性能的负面影响。)

2.抛开直接使用的语言实现,理论上,现在仍然没有一个靠谱的形式系统能通过区分代码和数据来显得比不区分代码和数据的系统更“安全”,虽然使用前者的系统的确有用于安全目的(主要是在一些验证工具的设计中作为发现而不是避免安全漏洞的理论基础)。

3.硬件角度来看,现今主流体系结构仍然使用不显式区分地址和数据线普林斯顿结构(原始版本的冯·诺依曼结构),而处理器内部的微架构会通过区分数据和指令缓存的形式过渡到哈佛结构,整体上算是混合架构。(历史原因有很多,没修过计算机体系结构的或者白学了的自己搜。)

划重点:包括苹果设备使用的CPU在内,主流设备在ISA层次都使用和主存布局一致前者。(非主流嘛……51单片机什么的……)

这一方面是软件(特别是操作系统)历史上的兼容性包袱,但根本的还是来自满足需求的可行性——你使用的操作系统依赖的、提供给app用的一致的虚拟存储抽象就是这样设计的,并且找不到什么伸缩性足够好、成本可以接受的靠谱的替代。(几个现代用户能受得了加一根内存条然后在运行每个app前把内存管理例程的参数挨个儿biu一遍嘛……)像单片机那样敲捣现在你用系统乃至app并非技术上不可实现,但现时不存在任何“生态系统”可以承受这种开发和部署成本。

回到之前的问题。苹果在这里做了什么?强制开发者在高层区分指令和数据。

这会造成一系列严重问题,比如说要实现一般意义上的热更新这样的特性,排除一直以来(严格程度上)或多或少被苹果禁止的方式,得需要额外的动态语言运行时;再 如不能使用JIT编译等手段发挥硬件机能(差别参照硬件加速的作用)。后者在浏览器这种应用上明显成了瓶颈,于是苹果给自家的Safiri开了洞,而别家浏览器就没这种差别待遇。这种利用作为OEM和系统供应商的优势垄断地位对其它厂商的不正当竞争,靠不对等信息操纵市场缩减用户选择权,摆明是非技术上都该恶心到最终用户的例子了。

另一方面,硬件实现混合体系结构,包括普遍支持的SMC(self-modification code)特性,是有显著的代价的。芯片授权费用的很重要一部分来自于设计成本,而设计复杂的存储子系统之后再绕过去显然是没事找事,效果就是用户花了的这部分钱打水漂,这是恶心最终用户的破烂之二。

当然,这可以归咎于开发者的一般意义的蠢;把软件撸得无谓地卡翔了,大抵也属于此类,只不过可以是无心之过而且没有那么突出的反智效果而已。

还有一点,苹果虽然能在应用商店的地盘以下架威胁变相强制策略,但没法顾及不以应用商店渠道发行的软件。注意这些软件可能是完全合法的(比如操作系统本身),照样可能会受到因为允许不区分指令和数据的形式的潜在的安全问题(如果有)的影响,也没有免责的锅能甩。

(……至少苹果的系统内部是干不掉实现类似dlfcn功能的这坨API的,所以技术上就别指望一定能有多安全了。)

于是所谓的安全仍然回到了对渠道的信任上,没有新意。(应用商店没有满足需求的软件你又不会自己撸?活该咯?高兴么?)限制使用唯一封闭的渠道这点对外行来讲或许不算什么大不了的,但对懂行的来说就是少了一个公开审计的渠道,在同等条件下即增加不安全性(即便任何此类机制最终都面临工作量过大根本审计不完的问题)。

https://www.bilibili.com/video/av34518556/#reply1150355393

(非贴吧,暂存。)

连个靠谱点的包管理器都没有,这就是你们手机党的所谓软件生态好?

苹果是沙盒机制,不需要软件管理器。安卓上各种流氓在苹果上都跟从良了似的,没有自启,没有杀不完的后台,我不要它了长按删除也不用担心残留,这还不够省心?iPhone给我的感觉就是我是真的在用一部简单耐用的手机,完全不用刻意折腾

我不反对你表达的某种用户的第一感觉有一定的代表性,但我需要强调,对全体用户来说,这是一种错觉。

首先,按你的讲法,这字面上恐怕还真不够省心——如果真要这种省心,那看起来更应该大力吹捧“云”系统,搞所谓SaaS,而不是继续在乎这个视频所讲的对于你省心的需求的已经不省心的实现细节。(顺便,你作为普通消费者的个体无法决定生产者提供这些选择是合理的,但这么多年了终端消费品市场在推动这个方向的有效实现上基本是失败的,却反而以此自满了,还要不站在你们立场上的我来另外补充,这至少说明“省心”在根本上还是徒有其表。)

其次,你以“流氓从良”的角度说明“省心”,也体现出这仅仅是相对比较发现明显的流氓行径比较少而已。决定需要什么软件是最终用户的决断;但制定安装准入策略需要系统管理员的素质(大多数用户不经训练并不具备)。而判断什么软件有非预期的安全风险(足以认定是“流氓”)需要的认知更接近后者——只是你觉得是流氓的软件已经流氓到路人皆知的水准了,于是放宽了流氓的门槛。就剥夺用户行使管理员权限来讲,各家厂商都好不到哪里去,但苹果尤其过分,某种意义上可说是有建制的隐蔽流氓中最强力者(例如可以做到干预立法鼓吹限制用户jailbreak的合法权力——而原本对拥有设备所有权的管理员来说这只是跟chroot对应的正常操作而已)。结果就是消费者集体用脚投票干掉流氓的能力越来越弱,厂商最终只能靠厂商来限制。这种对安全风险的随遇而安,降低流氓的门槛,跟省心的预期根本上是矛盾的。

最后,维持这种错觉的一个重要原因是用户暂时和产品经理的意思没有冲突。本来按理说,这种(以UNIX-like古董为代表)方案实现的复杂系统(却又没杜绝抽象泄漏而使开发者作为一种用户也仍然必须认识到这一点)足够要求用户具有一个系统管理员的常识,否则浪费的不可再生资源和最终用户取得的相比,足以让它成为奢侈品;而这种篡改定位的产品在市场中获得成功,还侵占了即使不是这种产品的用户的利益。这是在设备以外不得安宁的一个因素,无论怎么说都不省心。

为什么黑三星(的某些产品)

https://tieba.baidu.com/p/5555131286?pn=3 (一直被吃贴……)

不好意思,我不是针对谁暴躁。我是觉得被智熄设计玩过的都该有这样的觉悟的。

我不清楚诸位是不是足够享受过近几年如 note8 这种丧尸式内存管理下对普通应用的困扰——自动熄屏后打开 100 次有 95 次以上前台应用直接就给干掉了,有几次发现终于长记性了,然而一划屏就一夜回到解放前了……以下脏话省略。

更严重的问题是这类机器上一旦 root , KNOX 机制生效,用户就迫真活该损失行货保修了?本来用的好好的 Samsung Pay 突然就不能用了?活该我傻我买机器的损失咯?

特别注意,市场上卖的这种应用安全机制,就三星那么有自信直接封装 Snapdragon MCU 的 efuse 做成硬件上能修改但不可逆的玩意儿。这锅甩得 666 啊。

拜托,买的机器又不是租的,拿到的是整机的所有权,在消费者根本没有违反任何强制性安全标准更改物理实现的情况下,是谁给厂家强制用户选择不改动系统和不保修只能选择其一的脸的?是谁给厂家强制用户物理上不可逆地损失正常功能和正常运行体验之一的脸的?

几十年前搞密码学的就知道安全不能依赖算法保密,你三爽还敢冒天下之大不韪让用户指望名存实亡的物理隔离咯?说极端点,鬼知道授权经销商暗地里能交易到什么程度?别说你企业产品卖给个人想概不负责,开历史倒车挖公共安全墙脚懂吗?

话说回来,如果 build.prop 能自己编辑也就罢了。但是偏偏就是要 root 权限,而且这里要 root 一定程度上说得过去。那么换个角度讲,在 Android 之前,本来各个 Linux 发行版的系统管理员拿 root 权限就是很正常的事情(如果你是租的光猫之类的另论),印象中就从 Android 开始,突然就搞得 root 反而还不安全了?

这里从某种意义上更欠黑的还是苹果, jailbreak 本来是个中性的技术手段,莫名其妙就被 FUD 妖魔化成非法了,谁带的节奏来着。

为什么黑华为(的某些终端设备产品)

https://tieba.baidu.com/p/5717092249

按已有风评,华为在音游漏键(屏幕实时响应)是普遍特别辣鸡。

只确认过个别机型,不知道现在是不是有改善,不过从尿性来看不要指望太多。

实际原因是:

  • 任务调度有问题,实测造成问题主要是华为应用商店不管是否强制关闭应用/停止服务/关闭自动更新都会不定时瞬间吃大量的CPU,并且强制关闭后不断自启;因为是系统应用,无法卸载禁用。
  • 稍微一热处理器性能打折严重。

理论上讲 root 能治,这点比三星厚道点,因为 root 了还能刷回去,不是不可逆的。

其它待挂列表

Google C++ Coding Style 槽点

有人已经挂过了。考虑到材料不断更新,可能需要重新估算工作量。

实例:所谓的static initialization order fiasco

Style, also known as readability

乱弹琴。

The term Style is a bit of a misnomer

不过之后似乎有自知之明……

Forward Declarations

A "forward declaration" is a declaration of a class, function, or template without an associated definition.

冗余定义。

C system files.

C++ system files.

__USE_MINGW_ANSI_STDIO 呵呵呵……

Do not use a using-directive. Do not use inline namespaces.

无脑逗比,显然不知道这些特性干什么用的。放着 inline namespace 不用难道上 ABI tag ?

FRP

发明者对外延失去控制?

http://weibo.com/1684815495/CsFjTbcOj?refer_flag=1001030103_

被人嫌弃命名烂

http://stackoverflow.com/questions/5385377/the-difference-between-reactive-and-functional-reactive-programming

http://tieba.baidu.com/p/2286985797

找FRP的denotational semantics,结果随便搜就是挂婊的2333……

http://conal.net/blog/posts/garbage-collecting-the-semantics-of-frp

UTF-8 Everywhere Manifesto

以下评论基于中译版本

一些科普常识和结论(例如 UTF-16 因为变长特性导致的无能,以及代码页是不合理的烂设计)是没有问题的。但是,从动机、论证的目的以及参照的依据看,存在相当多的低级错误,应予视为误导。

1-3

第一个错误是混淆了“文本”的用途。文本首先是人类书写系统的直观具象。文本编码的最初设计也遵循这个基本要点,虽然之后作为妥协混入了“控制字符”也已有相当长的历史了,但那只应该是妥协。

文中提到文本作为通信协议表现形式的常用现状。这仍然只是妥协。注意通常称为二进制编码的非文本是无法被文本取代的,因为严格地来说文本充其量只是二进制编码的一种。原用于反映书写系统、之后作为妥协折衷产物的这种形式具有非常多不适合编码的普遍糟糕特性(例如浪费资源),只是体现不合理的 worse is better 渣设计的历史包袱的实例而已,其被普遍接受并非来自于自身的技术优势——换句话说,也是妥协。

造成用户不得不“享受”这种现状的,早年罪魁祸首之一是 UNIX 系为首的业界通行设计,近年来则是 Web 。 Unicode 承前启后不假,但在“流行”充其量是拾人牙慧—— UTF-8 几乎全是借 Web 的东风而大势所趋,尽管技术上来讲对 Linux userland 来说它更能发挥作用。

这些规范在标准化的意义上来讲相当不得体。作为其中影响最巨大的几个余孽(和妥协失败品),基于 UNIX 早年对文本处理的过于简化的错误假设, ISO C 长期以来没能说清楚什么叫“字符”(character) ; ISO C++ 也有类似的问题。(并且相关术语定义的混乱和微妙的不一致仍然存在于正式(normative) 文本中。)

4

不透明“参数”是此文较有建设性的论点,可惜并没有给出实用的做法。因为语言设计中普遍缺乏约束,这基本只能靠用户(程序员)自觉,效果没法指望。更正确的姿势是,对“字符集”和编码方案透明,减少字符集特性的冗余假设。这样,简单起见只有 ASCII 自然扩展才是 UTF-8 的核心优势。“完整” Unicode 支持从头到尾都是没有说服力的。

5

之所以 Unicode 有必要单独抽出来评价,是因为在概念混淆和把妥协强行扶正为正式惯用技俩推广的不可替代的消极作用。

技术上 Unicode 有显著的问题。作为妥协的以“字符集”为中心特性之一的规范,一个明显的笑话是“字符集”无法精确定义——因为它根本就没有(也没法)规定,什么“感官”“字符”应该是字符集的收录范围。

因为不是随便谁都能往 Unicode 里添加公认接受的字符,这导致了 Unicode 扩展不同于寻常的字符集定义的流程,缺乏文化和厂商中立性,并且可能会对实现添加麻烦。

近期较为显著的例子是 emoji 。一个笑话:为什么 FreeType 突然需要依赖 libpng 才能看起来使 Unicode 渲染的支持“完整”一些呢?

6

把传输编码和文本表示编码比较是一种常识性错误。应该逆转过来,从原始需求出发:如果一开始就做正确的事——就让文本编码尽量只表示人能读写的真正意义上的纯文本,那么这样的比较还有何意义?

反过来,正是因为这种不切实际(需求)的浮夸比较,进一步助长了对不适合作为通信协议的“文本”这种形式的滥用,这是一种恶性循环。

至于只用于真正“文本”的情形,作为东亚语言母语用户,我很清楚几乎从不需要英文字母以外的那些在 UTF-8 中占用两个八元组的西欧文字,作者对此非常地想当然。另外,真要论东亚语言占用空间,而不担心随机定位性质的话, GB18030 跳出来就能把 UTF-8 打得满地找牙(嘛,本是同根生……)。

7

真正优雅的操作是把编解码和对码位的操作分离——虽然会让人怀疑为什么究竟还需要编码这回破事了。

也就是说,这方面 UTF-8 永远被 UCS-4 吊打。

8

这里没有否认对感官字符的计数不重要,却有意无意地忽略了一点,在计数能可靠到显得重要的时候,码位和感官字符是 1:1 的。主要显著例子是没有复杂拼接规则的字母文字(如英文)和东亚语言文字。

绝大多数人口——不管是不是 Unicode 的用户——只关心使用这类文字。

还有一个自以为是的错觉是了解 Unicode Normalization Forms 对所有用户都很重要。文本的(最终)用户通常不应该需要理会编码细节——这点跟“程序员不需要成为一个语言文字专家就能够正确地写一个文件复制程序了”对比,莫名喜感。

9

UTF-16 当然很烂。

性能几乎不是问题

偷换概念。空间占用难道不是性能问题?别忘了为什么 UTF-32 没有把 UTF-8 干趴。

很多人认为不应该用文本通信协议,但实际上通信协议几乎都用英语和 ASCII 字符组成,UTF-8 更有优势。对不同类的字符串用不统一的编码使复杂度大大上升,更容易引发问题。

定义不清的“统一”的编码只会掩盖问题。“几乎都用英语和 ASCII 字符组成”也仍然打不死 GB18030 ,只能说是历史包袱原因。

我们尤其认为给 C++ 标准增加 wchar_t 是一个错误,给 C++11 增加 Unicode 亦然。我们更想要一个能存储任意 Unicode 数据的基本执行字符集。

这是技术错误。让 ISO C++ 引用 ISO 10646 并不十分困难,但要求基本执行字符集的想法不切实际——即便排除 Unicode 可变版本兼容的困扰,要求运行时分辨被编码的字符需要额外的开销并不能被任意实现接受——特别是处理“文本”毫无用处的应用场景上。而只是要求容纳码位,并不需要更改执行字符集。

实际上 ISO C++ 连 ASCII 都仍然懒得完整要求。说到这里是不是又得想想 UTF-EBCDIC 哭晕在厕所呢?

标准库的 facet 有一堆设计缺陷。

依赖区域的特性本来就是一坨笑话。

10

仍然通篇逃避区分内部和外部编码这类关于选型的文本程序开发人员的基本义务,反而把问题搞得更复杂,同时强迫最终用户承担本可能不必要的额外开销。

11

但既然存在,那么说明文本不一定是给人类看的。

更不说明应该存在。

操作子字符串的方法会兴高采烈地把非 BMP 字符一刀两断,返回一个无效的字符串。

为什么判断“字符串”“有效”必须以 Unicode 为准则?

底层框架选错了内部字符串表示的方法,影响了 ASP.NET 这样的 Web 框架:网络应用几乎全要用 UTF-8 来输出(和输入)字符,在大流量的网络应用和服务中,字符串转换导致了显著开销。底层框架选错了内部字符串表示的方法,影响了 ASP.NET 这样的 Web 框架:网络应用几乎全要用 UTF-8 来输出(和输入)字符,在大流量的网络应用和服务中,字符串转换导致了显著开销。

偷换概念。某些特定的应用层不等于“网络”。

更应该说的是选错了服务的领域。要求统一编码,只剩“外部”的场景,就不应该做适配的无用功。所谓物以类聚和 garbage in garbage out 对这里的场景更合适。

不论原来 UTF-8 是否在创造时是作为一个兼容性措施,现在它比任何其它 Unicode 编码更好,也更流行。

“更好”再次被 GB18030 打脸。顺便,到现在 UTF-8 也不是任何一个官方使用东亚语言的政府强制要求支持的标准。

你不打算让你的软件设计完整支持 Unicode,是在开玩笑吗?

指望多数用户的大脑能“支持”非 BMP 字符才更加可笑——事实上是 BMP 字符也太多了。

另外同上,因为外延的不确定性, Unicode “字符集”论及“完整支持”,也是彻头彻尾的笑话。敢明确版本嘛?

那么,既然你打算支持,而非 BMP 字符罕见就不支持,除了给软件测试增加难度,实在没什么好处。

支持才是明显增加测试工作量。

然而,真正值得在意的是,真实的应用程序不怎么操作字符串——只是原封不动地传递字符串。这意味着“几乎定长”几乎没有性能优势(参见“性能”),让字符串短一点倒有可能挺重要。

莫名其妙的逻辑。码位计数可用且码位和感官字符是 1:1 时,分隔完整字符边界的操作是 O(1) 还是 O(n) 在长字符串上性能差异可能相当明显。当然这看来的确不很常见,但这恐怕也是 UTF-8 在编码东亚文字文本上唯一打得过 GB18030 的一个技术理由。

我们坚信,只要让软件 API 默认使用统一的主流编码,程序员不需要成为一个语言文字专家就能够正确地写一个文件复制程序了。

回顾上面的 normalization 打脸。

但是如果你将来打算加个配置文件或日志文件,请考虑把字符串都转换成窄字符串。这样可以免除后患。

这是常见的鸵鸟方法论。正确的做法是之前隐晦提到过的,区分“内部”和“外部”编码方案——如果不能自己发明设计,至少也需要了解应用。这才是处理文本和其它数据的程序员需要做且必须擅长做的基本功课。

我们无法接受,即使是在像文件连接这样简单的情形下,所有现有的代码都得注意到 BOM 的问题。我们无法接受,即使是在像文件连接这样简单的情形下,所有现有的代码都得注意到 BOM 的问题。

这则是彻头彻尾的愚蠢

注意 UTF-8 的 T 的含义。所谓字节序标记本质上是传输时必要的元数据。只有附加假定以字节流传输,才可以省略字节序时指望正确的数据流——但是不一定包括边界。正确的传输总是需要这种附加的 cookie ,区别无非是哪一层做罢了。提供字节序是对边界的一种附加的(不完全)强制验证。在必须考虑边界时,丢掉附加验证并不会更“简单”。

另外,当处理“文件”这种作为外部传输的单独映像时,二进制文件的所谓文件头的元数据基本是不会被任意丢弃,处理“文本”附加额外的不一致的抽象,对“简单”而言,得不偿失。

只有假定没有边界(如 UNIX 的 cat 传输的数据)时才需要显式丢弃元数据的干扰。但这种缺乏类型标记的非结构化数据传输即便是对 shell 编程这种用途来讲,都是不怎么靠谱的,基本上只适合用完即弃。

另外, Unicode 标准实际上并没有显式鼓励传输时丢弃字节序的做法——在数据是已知 UTF-8 有效载荷的情况下,加入冗余 BOM 当然是添乱,所以应予避免。

永远使用 \n (0x0a) 作为行尾标记,即使是在 Windows 上。文件应以二进制模式读写,这保证了互操作性——一个程序在任何系统上都会给出相同结果。

依赖本不必要涉及的二进制表示通常是灾难的开始。

既然 C 和 C++ 标准采用了 \n 作为内部行尾表示,这就导致了所有文件会以 POSIX 惯例输出。

纯属谣言。

文件在 Windows 上用“记事本”打开可能会出问题;然而任何像样的文本编辑器都能理解这样的行尾。

而且,“实际”并不能保证行尾可信。记事本就是一个假定行尾二进制表示的反面例子。

至于为什么这么麻烦嘛……怪一开始的“妥协”好了。不过,考虑控制字符的设计, CR+LF 在含义上是比 LF 要合理的。

另外,实际上很多(应用层网络)协议就是规定用 CR+LF 而不是 LF ,为何在此反复?

我们也偏好 SI 单位、ISO-8601 日期格式,用句点而不是逗号作为小数点。

SI 最近正在改基准单位定义, ISO-8601 一坨几种来着……

一个更稠密的编码就更有利于性能。

对东亚文字一如既往被 GB18030 吊打。

没有纯文本这种概念。没理由在一个名叫“string”的类里存储仅 ASCII 或 ANSI 代码页编码的文本。

然而 std::string 其实彻头彻尾存的是二进制数据,还能在中间夹杂 \0 ……

况且,MultiByteToWideChar 不是最快的 UTF-8↔UTF-16 转换函数。

作为撸过目前为止还没有发现更快的 UTF-8 → UTF-16 解码实现的,我可以负责任地讲,实际上相当慢……虽然通常并不介意这点差距。

将文件存为无 BOM的 UTF-8 可以解决。MSVC 会认为文件已经在正确的代码页,就不会碰你的字符串。

然后 MSVC 很高兴地会瞎猜编码了……嗯,如果不介意打开 IDE 就弹窗警告的话。

如果你打算设计一个接受字符串的程序库,只要用简单、标准且轻量的 std::string 就好。

“接受”和“储存”不是一个概念。

这种观念显然过时了,被 string_view 吊打。

如果你是 C 或 C++ 程序库作者,用 char* 和 std::string,默认 UTF-8 编码,并且拒绝支持 ANSI 代码页——因为这东西本来就不支持 Unicode。

负责的接口设计者应只在外部编码约定使用一致的普适编码,如 UTF-8 ,而避免作为实现细节的内部编码污染接口。至于 ANSI 代码页这玩意儿烂和 Unicode 无关。

12

这是我们基于经验和现实中程序员对于 Unicode 犯下真正的错误

然而对历史上导致这些设计的真正的错误却睁一只眼闭一只眼。

13

支持 Unicode 方可大幅提升用户体验。我们建议选择 UTF-8 而不是 UTF-16、GBK 或 GB18030 作为应用程序的默认编码。

这两句话连在一起,说得 GB18030 不是基于 Unicode 一样……(虽然 UTF-16 的部分应该不会被理解错。)原文并没有讲清的东西一笔带过,含义有些不清楚。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment