Skip to content

Instantly share code, notes, and snippets.

@FrankHB
Last active February 27, 2024 01:24
Show Gist options
  • Star 31 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save FrankHB/00731fedf07b4ea271afa70a5cdc8d9d to your computer and use it in GitHub Desktop.
Save FrankHB/00731fedf07b4ea271afa70a5cdc8d9d to your computer and use it in GitHub Desktop.
贴吧相关 FAQ 、资源链接和其它附带清单。

FAQ

包含若干待定整理内容。

待补充

const/常量表达式(constant expression) 的联系和不同。

参考:

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 都来不及呢……)

如果有能力读原著,那就考虑直接读原著,因为译者的下限会把书的内容质量下限再过滤一遍,而译者普遍水平比坐着更差,虽然不排除个别还算靠谱的。尤其应慎重选择教科书的译著以免先入为主。裘宗燕的翻译质量整体还行,但有些术语明显偏离惯用法。其它自己搜风评。
虽然吧规有给清单,不过我本人不推荐任何书,更反对没其它编程语言的经验直接上手 C++ 。你不是天选之人,没本事把一坨蠢书的 bug 都找出来,之后事半功倍就等着呵呵吧。

硬说的话, SICP 旧版和 CSAPP 打底,基本上看懂(不一定需要做题,看各人本事)以后学其它语言稍微容易点。讲 ALGOL-like 语言的国产书和带 Primer 的都更不推荐,稍微要上路点的,C 可能一本合格的都没有, C++ 看 Bjarne Stroustrup 的出版年份较近的入门读物。(我不给具体书名。如果找不到,说明基本不适合学这玩意儿。)但是你非得要“速成”,除了会啃权威文档习惯快速断章取义给书找 bug 以外基本没什么办法——跟某些没经验者的不同,这就是已知最快的方法(只不过对基础技能要求比较高,但说白了也就是一般专业要求而已),除非你有本事发明你学的东西(但即便是有,也就是快慢的区别,参考权威文献对这种 artifact 是批判的基础所以不可能省掉)。当然,LZ 可以抱怨短时间吃 3 本书强人所难,不过 LZ 的问题本来就是这个状况,何况就现在本科教师普遍连什么权威材料该参考都搞不清楚的教学质量,大多数专业 4 年出来的基本也都达不到这个入门水准。
关于 SICP 之类的前期入门问题,免责声明:我没全部审阅过。但是我还是没找到更好的替代,所以顺便解释一下。
虽然我是所有都自学的,不过路径和进度都比较奇葩(远超出科班教学规划并且直接能给当期教材审校的程度),所以本身不怎么有参考性。
例如,我正式学 C 是直接读的 ISO/IEC 9899:1999 (之前有一些 BASIC 和个别小的嵌入式设备的 C-like 的基础,也是翻手册学的),反过来看 C 的教科书都是各种惨不忍睹了,包括 K&R 。具体坑哪先不多展开。
然后,C++ 我翻了一遍 BS 的 TC++PL 100 来页,就懒得多看了,直接翻 ISO/IEC 14882 的 draft 对照最新进展来试验。
这些学习路径对一般教师和学生确实强人所难。
不过我发现我还是漏掉了一个优化,那就是 SICP 。当年有个出国的学生(记得是哲学类贴吧遇到的,现在反正不混贴吧了)百度消息问我里面的问题,我第一反应是“什么鬼”,还提醒应该多了解些“函数式编程”,现在看来可能是黑历史了,直到我自己之后重新开始自己设计语言,从 λ 演算开始开始推出“纯粹”的计算模型,才发现 Scheme 的设计显然的清晰之处。
(还有个理由是,论长度, RnRS 显然比 ISO 的裹脚布清爽多了……差了几十倍。这导致原则上我对 LZ 这样的没基础的同学入门 C++ 这样的玩意儿持悲观态度,因为不管是什么书,杂七杂八的内容的数量就放在哪里,没基础就意味着不知道什么算是入门,哪些只是为了入门就能跳过的而节约时间。)
这时候我重新看了一部分 SICP (因为早就对非 spec 类文献习惯性速读,习题全部跳过不评价),发现里面确实有不少东西讲得比几乎所有其它入门教科书都清楚。
不过,以现在的眼光看,主要仍然是前几章的内容耐看。之后的内容有更多其它专业文献参考,对使用的语言本身的设计的要求也比较高。很遗憾,大部分语言属于“不怎么样”的一类(比如没有 proper tail recursion )。
新版用 Python 的 SICP 在目的上实际出现了偏差。按 Gerald Jay Sussman 的说法,他想教的是 computer engineering ,而我这里说的纯粹就是 programming 的入门。
具体内容的问题,随便举个例子:

Statements & Expressions. Python code consists of statements and expressions. Broadly, computer programs consist of instructions to either

    Compute some value
    Carry out some action

Statements typically describe actions. When the Python interpreter executes a statement, it carries out the corresponding action. On the other hand, expressions typically describe computations that yield values. When Python evaluates an expression, it computes its value. This chapter introduces several types of statements and expressions.

从语言设计的角度来说,贸然把语句(statement) 和表达式(expression) 并列起来并不是什么好的讲法。
其实这里没有很好地抓住重点,而以至于偏差。
表达式来自并扩充了代数学上的一些习惯性表示,其目的起先是为了计算(computation) ,这点并没有错。
但是对非纯函数式的语言来讲,计算并不是使用表达式的唯一能表达的计算作用(computational effect) 。
在这里所有之前提到的语言的表达式的求值(evaluation) ,都是可能带副作用(side effect) 的——例如,输入/输出。这意味着一般的表达式同时可以 carry out some action 。
而语句确实经常只是为了 carry out some action ,但所有之前提到的这些语言中都是可以用表达式构建的。
所以把语句和表达式并列地讲,已经造成了先入为主的印象偏差。这不适合一般的“编程”基础(事实上,旧版 SICP 使用的 Scheme 就不是,下面会讨论)。
理论上正确一些的要点是,表达式包含的子表达式求值的顺序并不固定(尽管语言可以添加规则约定顺序),而“语句”必须是限定顺序的。这也是为什么语句对副作用的语言来说是个相对重要的构造,因为副作用(例如输入/输出)随意交换顺序可能会改变程序的可观察行为,用户通常必须指定顺序。而表达式则是一般地包含 compute some value 和 carry out some action 这两个职责的构造。
自以为已经入门的同学可以自测一下,有多少书本和老师会讲清这个的?如果真靠你自己的思考就能得到这个结论,可能就已经“入门”了。
至于我是在接触其它书之前独立通过 ISO C/C++ 的 function invocations do not interleave 规则发现的,后来才发觉这个就是理论计算机科学中经典的 applicative order (使用 currying 的话就无所谓 leftmost first )。
题外话,其实从上面的讨论也可以看出,人为地在语言中区分出语句和表达式本来就不是什么容易理解的设计。像 Python 之类就属于这样的一开始就不怎么容易理解的语言。
反过来,Scheme 是不强调语句的概念的。在 Scheme 中,顺序的表达式求值使用特殊形式(special form) (在 R7RS 改称表达式类型(expression type) ) begin 。也就是说,实质上的语句的功能在 Scheme 中是用一个非常局部的语言特性就能实现的,这并不是什么大不了的认识。
然而对 ALGOL-like 语言(以及 Python 这样的大杂烩)入门的更多人来讲,他们是很长时间中都没有意识到这点的机会的。所以他们以后学不是这样设计的语言时,就要付出本该避免的更大的代价来摆脱思维定势。
当然,有的同学可能会问,首先用语句来作为最普遍的基本结构不也是很正常的吗?
这个问题也很简单:即便你有了语句,现实中也摆脱不了表达式。表达式具有子表达式这种递归的语法和对应的求值规允许以很小的代价使通过组合复用计算结果,而语句只支持顺序的描述,并且常规用法中不能直接把它当作表达式来用。
只有类似语句而没有类似文法的表达式结构,是没法表示灵活的抽象的,于是使用这样的语言,就被迫发明类似的高级语法扩展。
常见的大概也就一些汇编了(然而一般的汇编器实际上还是支持表达式的,还有更高级的抽象,所以事实是摆脱不掉;都有嵌套递归语法的设计在里面)。
这样的语言中,顶层上基本也是不符合结构化编程(structure programming) 的要求的,以至于只使用这样的构造,没法在现实中使用。(直接点说,和 SICP 要介绍 function 进行抽象的做法是矛盾的。)
结果,你学习这样强调语句为描述操作的基本单位的语言(虽然不严格,通常所谓的指令式语言(imperative language) 都是这样),需要同时了解表达式和语句,有的时候可能还要纠结什么时候使用语句,而没有清晰地了解或者忘记“我需要顺序就使用语句”这个本质需求。
作为普遍现象,这样的需求错位还带来了语言从设计到学习上的混乱。一个经典的冗余设计是 C 的 while 的条件,因为语法设计没法往里面塞语句,就需要用逗号表达式往里面实现语句的功能。(不像 C++ ,还有逗号重载而显得区分起来有点意义,虽然那个工程上基本上更恶心而更糟糕。)
让这样的语言设计流行起来,入门的错误姿势大概“功不可没”。语言的设计者也得通过对语言的学习入门,并且先入为主地对这样的语言更熟悉,于是市面上这样的语言就更多。所以,“工程”方面就更容易见到这些语言。但是,以“编程”为目的入门不应该受到这个结果的影响而增加工作量。
类似地,还有强调 proper tail recursion ,以及强调递归比循环更普遍(类似表达式比语句更一般化)也是常常会被忽视的理论计算机科学的传统。
这样做在表达计算目的和简化语言设计上其实是有很明确的目的的(虽然原理比较复杂,暂略),而许多语言通常没有独立的理论基础(例如操作语义模型),会漠视这个需求。
除了传统指令式语言的流行,这很大程度可能是“工业界”语言的设计者本身缺乏学术背景和训练的原因,以至于看不清这样的思路来简化更一般的设计问题。
像 Python 的设计者 Guido van Rossum ,早年就分不清 proper tail recursion 和 tail call optimization 的概念(这或许还情有可原,很多搞 Lisp 的都没分清),而且还以“难调试”作为不支持的借口,贻笑大方。
为了文明用语,暂时先不对其它具体讲法进一步长篇大论了,有问题可以个别提问和讨论。
还有一段 disclaimer 漏了,补充给了解 Scheme 的同学,虽然我认为 Scheme 很多地方设计得更正确,不过请勿在这个议题上误认为我是在单方面鼓吹 Scheme :
就 begin form (其它 Lisp 可能用 progn 代替,对 C-like 语言来讲就是内建的分号——原则性表示“语句”的分隔符),Scheme 的具体特性的设计其实也不咋地。
事实上在 RnRS 的 derivation 中,可以看到 begin 使用宏基于 lambda 实现的。也就是 Scheme 的 lambda form 其实蕴含了“顺序”求值表达式的作用,在我看来违反了单一职责原则,并且 parse 起来容易错(可读性上还不如分号+}来得显眼)。
我已经在类似的语言中实现了不用任何原生顺序结构,只用函数调用的 applicative order 就派生出来类似 begin 的操作。(这种做法首先来自 John N. Shutt 的 Kernel ,不过我做的不要求 GC 。)
说起来倒是跟 Haskell 用 monad 实现 do-notation 有点像,但我全然不喜欢 monadic 的提法,因为说白了起作用的就是某个 monad law 但实际上应该叫 applicative 性质的东西而已。

TODO 可以加不少链接。

范型

模式(pattern)

编程语言中,所谓的模式是指重复的语用习惯的不同表现。

并非所有语用习惯的表现都是模式——一般地,这些表现包含架构模式(architecture pattern) 、设计模式(design patterm) 和惯用法(idiom) 。其中惯用法是指表现能直接通过语言特性进行封装而复用的语用方式。模式是指无法归纳惯用法的非平凡的(trivial) 表现。

模式中,架构模式通常和系统设计的整体描述相关,不论使用何种语言实现,都难以用语言特性直接描述,而在不同特性乃至不同范型的语言中都存在对应的抽象形式大体一致的表现。而设计模式和惯用法类似,更依赖语言特性。

严格地说,设计模式是语言设计缺陷产生的瑕疵(artifact) ,因为这种状况既难以通过语言原生表达又难以推广到语言中立的更上层模型,而不利于进行解决方案的复用。因此有理由认为,适当设计的通用目的语言应能避免设计模式,使上述语用表现趋向于被归类为架构模式和惯用法之一

类似的其它观点:

另外的一些具体表述:

基本(primitive) 和派生(derived) 特性

要点:

语言特性分为基本特性和通过基本特性经过确定的方式组合得到的派生特性。

一个良好设计的语言是模块化的,即合理地使用基本特性派生特性,而非进行特性的堆砌(另见 RnRS )。

最一般意义上,描述语言规则的元语言应是被描述的对象语言的一部分。元语言使用公共的基本特性,派生出对象语言的其它特性。只有足够必要同时被元语言和对象语言复用的特性,才是基本特性。这样有助于维护语言设计的简单紧凑。

举例:if 应是基本特性,因为难以使用其它特性派生。

https://pozorvlak.livejournal.com/94558.html 等暗示的不同,Smalltalk 派生 if 并非是值得借鉴的正常设计。这种的表达能力实际上依赖基于继承的面向对象模型这种复杂的元语言设施,描述这种元语言的可编程性需要蕴含 if 的特性,不比 if 更基本。选择 Smalltalk 的思路以弱化抽象能力作为维持表达能力的代价,无助于降低整体设计的复杂性,反而提升了使用元语言的门槛。

和 Smalltalk 不同,λ 演算可以编码布尔值并实现 if ,这种派生是简单的。但是,这依赖于语言不能表达依赖顺序的副作用(非纯计算)的假设。特别地,常规语言中的 if 在非纯计算的表达式只求值一个操作数而选择求值其余的操作数,不能简单地通过修改 λ 演算的演绎规则实现(因为任何演绎规则的表示首先都需要引入区分可规约表达式规约状态的附加项,使用 λ 演算描述需要引入几乎只在描述元语言时有意义的特设的复杂规则)。

另一种派生 if 方式是在元语言中使用翻译成带有显式续延(continuation) 的形式。但是,这种方式要求类似的上下文中总是用(近似)CPS 的表示,引入不必要的复杂性,同时高度依赖使用续延的实现方式(例如,是否使用异步调度)而泄漏了抽象,大大提升了整体设计的复杂性。续延更适合用单独的基本操作提供为一等对象。

综上,使用一个更复杂但计算上更底层而足以在元语言规则中原生地蕴含(允许区分可规约表达式是否已被求值)的规约状态以及显式的求值操作的模型,派生出同时蕴含 λ 演算这样的经典模型在编程语言中需要的功能,是更合理的通用语言设计策略。这样的模型应能允许对象语言的求值算法在元语言中仍然较简单且避免基本特性的数量过度膨胀,并在必要时添加扩展以描述续延等基本计算机制。一个模型的示例是 vau 演算,对应 Kernel 语言

所谓函数(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 :
    • 通常翻译成“内存”,有一些问题。
    • “内存”和“外存”相对,指的是在线(online) 存储,只有和计算或控制部件联机时保证可用。
      • 对应物理实现的特性上,在线存储可以是易失(volatile) 的,断电后数据不保证持久;而外存要求断电时能长期保存数据(当然,长期是相对的,例如介质的物理保存时间有限制;这里的“长期”通常是指可以满足离线存储备份和迁移数据的需要)。
      • 内存通常响应远远优于外存,单位存储成本也更低。也有非易失存储用于混合内存和外存使用场景的硬件产品
    • 而 memory 指的是比“内存”更一般意义的存储——其物理实现方式包括寄存器(register) 、缓存(cache) 、主存(main memory/primary memory) 和辅存(secondary memory) 。
    • 主存是特定于编程模型使用的在线存储。
      • 因为常规的编程模型不要求资源持久(persistent) 存储,常规实现首先是内存。
      • 其次是以虚存备份到外存的形式(详见下文)。
      • 主存和内存也时常被混淆。虽然大部分情形主存即主内存,但也有辅存形式的内存,如 NDS Slot2 Memory Extension Cart 。
    • memory 也可以指硬件实现的器件,即存储器。
    • 翻译成“记忆体”也许字面上更贴切,虽然言语习惯有些问题。
  • 虚存(virtual memory) :
    • 被误会最多的基本概念之一,大约是因为 Microsoft Windows 中的错误用词流毒过广……
      • 该环境下的任务管理器和 Process Explorer 等管理应用还有工作集(working set) 等概念和操作系统理论上的微妙的不同,不过用得比较少先无视。
    • 使用地址翻译(address translation) 的方式管理被编址的存储,底层的存储(一般称为物理存储(physical memory) )映射的上层存储的目标就是虚存。
    • 因为地址翻译通常需要涵盖大部分访存操作,开销可观,通常存储管理单元(MMU) 硬件实现。
    • 硬件实现的虚存在带有 ring-based security 的大多数现代 CPU 中通过保护模式(protection mode) 强制要求。主流现代分时操作系统,都使用虚存为正常运行的前提,引导前期初始化后关机之前根本无法关闭。这也包括现在的 Microsoft Windows 的情形。
    • 虚存的底层存储通常是内存,但可选使用外存作为备份存储。
      • 经过虚存的统一地址空间编址等间接抽象,内存和外存共享统一的访存接口,内存在此相当于外存的缓存。
      • 外存一般不预期常用,而且即便使用了和内存性能差距不能过大,否则会引起严重的性能开销。
      • 当前只有少数体系结构设计的编程模型默认不区分内存和外存,即默认外存会参与统一地址空间(称为单一层次存储(SLS),以 IBM System i 为代表。
  • storage v. memory :
    • “存贮”和“存储”?不过一般还是都翻译成“存储”了。
    • 和 memory 不同,storage 指抽象的存储方式,而非器件。
  • RAM/ROM/... :
    • 存储的访问特征的分类。
    • 但更一般地指物理器件(存储器)的分类。
      • 应注意虽然 ROM 作为存储器具有只读性质,但 EPROM/EEPROM/Flash memory 等存储器配有对应擦写操作技术,而也被作为可写的非易失(持久)存储使用。
    • 偶尔会指设备预置存储器的分类。因为这类设备中包含两种容量对用户感知最强的存储器以 RAM 和 ROM 实现。
      • 更准确的说法通常是:RAM 指主内存,而 ROM 指设备内部持久存储。后者本质上是外存,尽管总是和设备一同加电而当作内存使用,但通常可感知地更晚加载(如内置 SD 卡实现的 ROM 存储),而不是和经典的内存一样在系统初始化时立即可用。
  • 分配(allocate) 和去配(deallocate)
    • 分配是指确保存储空间可使用的操作。去配是声明不再需要使用指定的存储空间的操作。
    • 通常分配来自于底层系统,去配归还对应的资源而使其之后可用(可能分配给其它任务)。
    • 底层系统保证分配成功时,对应的操作在抽象上可能被省略。
      • 程序使用静态预分配的存储空间时,可以认为属于这种情况。
      • ISO C 和 ISO C++ 的自动对象不被认为是“分配”的,尽管实现意义上典型地需要修改调用栈指针预留或释放调用栈空间。( ISO C 和 ISO C++ 在此的处理方式是如果实现无法分配,则引起未定义行为。)

默认 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。静态语言混淆“尽早”和“静态”地约束契约(被静态语言类型系统弱化为所谓的“类型检查”)在通用目的语言设计的意义上是没有出路的。

什么是多态(polymorphism)

大多数人没有经过系统的程序语言理论训练,对不同语言涉及到的所谓“多态”概念认知经常混淆不清。缺乏基本知识的理解,只习惯个别具体语言指定的少数“多态”的特例以偏概全,是缺乏全面理解导致的常见症状。不同语言之间的规格说明对此的描述也存在差异,加剧了这种状况。

因此,有必要补习基本概念以避免偏差。

另见早期来源

什么是接口(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/5948043210

纯虚函数有什么用?

无非就是构造一个不能有完整对象只能作为基类子对象的类,所谓抽象类。如果除了析构以外的非静态成员函数都是纯虚函数,就是所谓的接口类型。

注意C++里的和Java跟C#不同,一是析构函数,二是允许定义,可以非多态的的方式直接带 :: 当非虚函数调用。

不过这种接口就是面向对象的意义上也只是一种实现。C++自己就支持另外一种——模板(特别是类模板和别名模板,以及基于模板的元函数)。

基于结构化类型(structural typing) , C++ 允许类模板定义中引用依赖具体实例的未被声明的成员。这也能起到接口的作用,直至实例化时确定具体的实例。所谓静态多态(static polymorphism) 用到的就是这种手段。这个意义上, C++ 中不认为是具体类型(所谓的高阶类型(higher-order type) ,包括不同种类(kind) ——在 C++ 中主要是模板类型参数提供的参数多态中引入的带有全称量词的多态类型,还包括模板非类型参数提供的依赖类型和模板模板参数提供的更高阶类型构造器(type constructor) 构造的结果)的实体就成为了“抽象”类,而具体特化的类型是具体的类。(注意子类型多态和参数多态的相似性。另外,尽管机制不同,可以对比 Haskell 的类型类(type class) 理解其相似性。)

实际上,不管是禁止抽象类具有完整对象,还是禁止高阶类型不经实例化出现在普通的类型上下文中,都是人为的设定。抽象类要真能有实例也无妨,就是“无意义”和容易用错而已。不过高阶类型倒是有其它明确的用途,而用纯虚函数实现的抽象类能有实例就没啥区别了。不过就 C++ 来讲这里真正造成麻烦的是实现上的原因:高阶类型不实例化缺乏元数据规范;抽象类作为一等类型(尤其是作为函数参数和返回值时)只会让复制初始化的语义变得混乱。当然,后者存在的意义更弱——即便没有抽象类也有普通的虚函数允许实现子类型多态;而因为模板实例化是一锤子买卖,砍掉高阶类型就基本没什么模板了。

什么是面向对象

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

面向个蛋蛋……看了一溜儿回复就没个把 OOAD(面向对象分析与设计)和 OOP(面向对象编程)分清楚的,就别想搞清了。

关于 OO(面向对象)的口水话题,你需要先知道OOAD用于建模,OOP 是 OOAD 的编程实现,但并非只有一种实现而且必须要语言特性支持才能实现 OOP 。

OOP 在语言中的实现历来就有阵营的问题(至少有基于类的(class-based) 和基于原型的(prototype-based) ),你就算“精通”了 C++ 的所谓OOP,也会有人怼你,一厢情愿以理解清楚OOP是什么这个目的来OOP,没有OOAD的基础,基本是毫无现实意义的。

你这里所谓的OOP,主要照搬的Simula,在C++就是 class-based 的,用PLT(编程语言理论)业界的话就是以 subclassing 作为 subtyping 实现带有inclusion polymorphism的subclassing的特性支持。OO业界的行话叫 Liskov substitution principle 。

上面是少数在技术上强行能够扯清楚无争议定义。其它的OOP,随着不同语言和实现到处都在口水仗。

就 OO 不管 OOP 而言,公认面向对象的祖宗是 Alan Key ,语言有比较直接支持的像Smalltalk和ObjC,整天讲什么“消息”之类,跟你在 C++ 里用的有间接的对应但没什么直接关系。

也有其它的一些外部行话,但这些行话的不恰当使用和错位导致很多严重的错误认知(冷笑话:其实就很“外行”……)。

因为泛泛而谈过于抽象,这里讲几个体现鸡同鸭讲的例子。

体会一下,知道大部分人在说这些话的时候和不懂装懂没多少差距即可。知道更多的术语并不蕴含更清晰的想法,进不进坑自己考虑:

1.所谓的对象(object) ,实际上在 C++ 本来就是个最基本的概念,简单说就是指(带类型的)存储;而面向对象和Java之流的所谓面向对象语言中,对象是指类的实例(instance) 。

在class-based OOP的静态强类型语言中,类用类类型(class type) 表示,类的对象就是具有类类型的实体(entity) 。 C++的类类型的对象(C++意义上的)恰好这样的类的实体,于是就是面向对象的对象;但反过来不是——例如,Java所谓原始类型(primitive type) 的 int 值就不是 Java 意义上的对象,但可以是 C++ 的 int 类型对象。

(注意严格意义上 class-based OOP 也并没有阻止你把 int 类型的值当对象,这种限制只是 Java 之类的说法;可笑的是被很多一知半解的所谓面向对象语言的用户当真了。)

有的人说“C 语言没对象”,这也是极可笑的,恰恰反映出对 C 一窍不通:“对象”是 C 几乎最核心的概念(也是 C++ “对象”概念的直接来源),尽管和面向对象没关系。

而且,C 虽然不是所谓的 OOP 语言,C 用 struct 代替 class 也可以实现许多 C++ 的 class 特性(事实上,兼容 C 的 struct 在 C++ 中就是一种类类型)。

例如,C标准库的 FILE 在这个意义上就显然很 OO ,而且是 class-based 的。再如 GTK+ 之类就是这样用 C 明摆着来 OOP 的。

2.所谓的实例化(instantiation) ,在C++就是指模板生成模板实例(template instantiation) (可以是类或者函数或者变量之类的东西)的一种机制,跟 OO 无关。

Java 等一些语言才有把作为类实例的对象(对应C++的类和类类型对象)钦定为 OO 意义上的“类实例”。

3.所谓的多态(polymorphism) ,实际上就是 PLT 所谓的 inclusion polymorphism 。这种多态实际上算是常见的多态中最年轻的。

C++ 事实上包含更老资格的多态:重载属于 ad-hoc polymorphism ,模板支持 parameteric polymorphism 。不过很遗憾, ISO C++ 的行话中并没有考虑这个,只是简单地法定 polymorphism 是类之间的关系,实际上就是缩小了外延而制造了更多的误会。

这样,脱离 C++ 的范畴,把多态当作 OOP 特征的自然是多重笑话了:偷换 OOP 为 class-based OOP ,又不清楚什么是真正意义上的多态。

关于抽象和封装

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

我不知道原文,GoogleBooks 搜不出第一段的内容(搜到的全是一整页“8”看起来是个章节标题,没兴趣猜关键字了),不过看上下文这是胡扯。

因为提到了什么“自由的运算符”和“完全的友元类”,这里的对象显然指的是 C++ 的类类型的值,对应 OOP 的对象。

这里的知识点是个很简单的常识:C++ 的类不是封闭的接口单位。友元(不管是不是类)或者非友元的自由函数都可以设计为是关联的类的接口的一部分。这里没提的是这样设计还允许接口不依赖特定的类(像二元操作用到两个类的情况就不用纠结到底是算哪个类的接口了)。

但这和类或者 OOP 的对象或者你要设计的程序多大规模都无关,只是 C++ 中除了类类型以外欠缺普遍的如友元类一般的扩展能力,你要换个 enum class 之类这里的原则照样适用。

接下来的说法就没多少参考意义了,因为如何保证封装程度很大程度跟允许使用的语言特性相关(特别是只习惯C++这样静态类型的静态语言的作者惯用的片面的所谓“封装”——因为欠缺变通能力)。

对 C++ 这样的语言来讲,滥用技巧从来都不是问题,问题是从来就不能保证应付代码的人跟得上代码作者的节奏,而不得不把很多时间浪费在建立(和这里的普遍扯蛋说法一样风格的)约定上,从而质疑选择 C++ 一开始就是个彻头彻尾的工程失败。

实际的大多数情况下,要求使用 C++ 的人并不保证能够足够灵活地使用这些技巧,也根本没有能力负责解决这些问题。所以锅从选择 C++ 上变成 C++ 上了,如果你不阉割你自己使用技巧的能力或者保证只是在跟风的话。

我的见解:

1、一个类通常表达一个完整的概念。(不是无法表达,只是表达的概念有大有小)

2、若要实现一个类,就至少要定义变量、函数、运算符三者之一。

3、抽象是对一些相关的较小的概念的概括出较大的概念。

4、封装,就是保护底层的变量、函数、运算符不被用户随意使用。(你当然不希望内部维护的指针被用户篡改)

你的理解全是洞。虽然不是你的问题(也不只是你的问题),不过考虑到挺经的,统一回复,酌情加套餐。

1.在C++的上下文中,特别是使用C++的实现中,要特别小心明确OOAD的class映射到C++的class这种OOP的实现方式并没有涵盖所有C++ class的用例。 所以关注的是设计的来源,而不是想从代码里映射回去。例如:

using I = std::integral_constant<int, 0>;

显然这里I是一个类,但没事非得逆向回去 OOAD 出这是一个什么类的概念,这是找抽。

2.无稽之谈。

std::input_iterator_tag:喵喵喵?

即便你非得说 OOD 的概念, 也没谁阻止你这样做——在 OOP 之前其实根本无所谓变量、函数、运算符。

3.抽象出来的外延可以和被抽象的东西“一样大”。

另外引用一个关于 PL 的 formal 之前的说法

Our programming-language use of the term abstraction does take a bit of getting used to, because we usually expect something abstracted to be smaller than what it was abstracted from. The abstract of a paper is a short summary of it. An abstract thought has left behind the details of concrete instances — though it seems the abstract thought may be somehow "bigger" than the more concrete thoughts from which it was drawn. In our case, the extended language is probably equi-powerful with the base language, and therefore, even if some specific implementation details are hidden during extension, the two languages still feel as if they're the same size. This is not really strange; recall Cantor's definition of an infinite set — a set whose elements can be put in one-to-one correspondence with those of a proper subset of itself. Since we rarely work with finite languages, it shouldn't surprise us if we abstract from one language another language just as "big".

4.封装是直接来自可组合性(蕴含维护系统不变量)这一工程需求的抽象实现结果。

这和OO或者分层实现架构的概念并没什么关系。

所以,“底层”之类的限定虽然是常见情况,一般意义上只是“系统的一部分”。

补充说明:

前半段比较容易理解的部分的举例(有些经验的 C++ 用户应该都不太难想得出来):

设计一个类,OOD 的做法是一坨东西和类关联,但具体 OOP 实现中不一定直接对应。

Java 习惯什么东西都往 class 里塞,而 C++ 原则上是反对的,因为确实有不太方便塞进 class 里的东西——硬塞进去就不自然了。

//! \note 类 C 和相关非成员API构成的一个封装边界。
//@{
class C
{
size_t size;
//...
};

//! \relates C
//@{
//! \note 这确实算不上个好例子;对简单的 getter 更常见的是放在类定义里。不过放外面还是能体现改起来不用管类定义而某种程度上更模块化的效果——代价是访问非 public 的东西还是得事先在类里加 friend 函数声明。
inline size_t
get_size(const C& c)
{
return c.size;
}

//! \note 这玩意儿要不是 std::ostream 过于周知,你都完全可以不放在边界里。
C&
operator<<(std::ostream&, const C&);
//@}
//@}

/*!
\note D 是另一个已知的类类型;这玩意儿你为啥要纠结塞到 C 还是塞到 D 里?
\relates C
\relates D
*/
std::common_type_t<C, D>
operator+(const C&, const D&);

后半部分比较不太容易理解(因为需要了解不太常见的语言使用)的例子:

封装问题的关键点是,这和 OO 本质上没关系。但是因为 C++ 等语言的泛滥,很多人把类的数据成员的访问限制控制作为封装的唯一手段了。

这是显然的本末倒置。程序设计提供的封装的用意是提供有明确使用规范和目的而不泄漏实现细节的接口抽象;C++ 这样的语言惯用的方式的“封装”仅仅只是一种实现方式——这种理解方法本身就是一种破坏“封装”式的对“封装”这个概念的曲解。

以下引用 tc39/proposal-class-fields 的 FAQ 举例说明存在一些不同风格的封装方式(这个提案出发点就是觉得下面的已有的方法不够好而要学类似 C++ 之类的 private fields ,不过以 # 表示 private 的语法好像引起公愤了……)。

How can you model encapsulation using WeakMaps?

const Person = (function(){
const privates = new WeakMap;
let ids = 0;
return class Person {
constructor(name) {
this.name = name;
privates.set(this, {id: ids++});
}
equals(otherPerson) {
return privates.get(this).id === privates.get(otherPerson).id;
}
}
})();
let alice = new Person('Alice');
let bob = new Person('Bob');
alice.equals(bob); // false

There's a potential pitfall with this approach, though. Suppose we want to add a custom callback to construct greetings:

const Person = (function(){
const privates = new WeakMap;
let ids = 0;
return class Person {
constructor(name, makeGreeting) {
this.name = name;
privates.set(this, {id: ids++, makeGreeting});
}
equals(otherPerson) {
return privates.get(this).id === privates.get(otherPerson).id;
}
greet(otherPerson) {
return privates.get(this).makeGreeting(otherPerson.name);
}
}
})();
let alice = new Person('Alice', name => `Hello, ${name}!`);
let bob = new Person('Bob', name => `Hi, ${name}.`);
alice.equals(bob); // false
alice.greet(bob); // === 'Hello, Bob!'

At first glance this appears fine, but:

let mallory = new Person('Mallory', function(name) {this.id = 0; return `o/ ${name}`;});
mallory.greet(bob); // === 'o/ Bob'
mallory.equals(alice); // true. Oops!

How can you provide hidden but not encapsulated properties using Symbols?

const Person = (function(){
const _id = Symbol('id');
let ids = 0;
return class Person {
constructor(name) {
this.name = name;
this[_id] = ids++;
}
equals(otherPerson) {
return this[_id] === otherPerson[_id];
}
}
})();
let alice = new Person('Alice');
let bob = new Person('Bob');
alice.equals(bob); // false

alice[Object.getOwnPropertySymbols(alice)[0]]; // == 0, which is alice's id.

可以看出,具体封装的实现方式及其限制是和具体语言特性高度相关的。

但是,基本上总的思路都是提供某些信息隐藏的方式,以允许用户限制不愿意作为公开接口部分的代码。

这里替代方式实现有问题,不是这种思路的锅,而是 JavaScript 的一坨乱七八糟的特性组合太不可控……

比较正经的另一种设计是直接基于类型提供封装策略保证封装的原始目的。

约定基于类型的封装是一个类型,其(公开) API 同时满足以下封装性要求:

1.已经在设计中指定的 API 是实现允许提供的 API 。

2.允许实现提供不涉及被封装类型的未在设计中指定的 API 。

3.允许实现提供通过组合1.和2.的 API 可实现且不改变非错误条件下行为的 API 。

4.允许实现提供涉及被封装类型的子类型的判断谓词,仅当这个子类型的对象在设计中已经指定的 API 的操作行为和被封装类型的对象行为不可分辨。 (在面向对象的术语下,4.被称为 Liskov 替换原则,但子类型实际上并不一定需要子类实现,这并不是 OO 专有的。)

基于这样的要求可以提供类似类/结构体的记录(record) 特性,保证被创建的类型总是封装类型——只有创建时的操作才能访问内部状态。例如,Kernel 语言中的 make-encapsulation-type 。Scheme 中 define-record-type 实际上提供了更多的功能(而更接近 C++ 的 class 定义),这侧面体现出实现基本的封装性其实根本不需要 C++ 那么复杂的特性。

具体操作中可以有更一般的信息隐藏手段强化这种封装性。例如,支持一等环境(first-class environment) 的语言可以约定直接限制环境中名称访问(不可遍历)来提供封装。

可以证明,上述保守设计没有 JavaScript 那么混乱的问题,也不太有必要像 TC39 照搬传统语言的设计了。

封装和面向对象

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

所谓封装(encapsulation) 是面向对象的特性?这可能是封装被黑得最惨的一次……

封装强调隔离模块化的接口和实现,并非面向对象独有。在 class-based OOP 中使用访问控制来屏蔽类外部对内部实现的访问而实现部分的信息隐藏以支持封装只是实现封装的一种手段。

例如,TC39 在 ECMAScript 这种传统 prototype-based OOP 的语言中加入类似特性,其背后的理由就是提供封装支持并且之前的其它封装手段不够用。这至少能直接说明,在支持 class-based OOP 的 private field 之类的封装之前,显然并不是没有别的封装方式。

用 class-based OOP 的封装的方式相当依赖具体的语言特性,这种特定的封装实现方式相比封装自身的重要性来讲,并没有太大的讨论必要。

属性(property)

(注意 attribute 翻译也是“属性”,和这个没关系。)

属性(property) 有一般本体论上的含义,但在 OOP 的范畴中,就是某些 class-based OOP 的用户生造的派生(derived) 概念。

一个接近的另一个通用概念是数据域(data field) ,不过后者并不只是在 OOP 圈中通行。

在 OOP 的意义下,属性反映了对象的状态的抽象。这相当于只允许特定受权访问途径的带有限制的数据域。进一步地,属性不要求状态的实在性,即以数据域+访问控制实现时,不要求存储。

可能是这种使用方式在抽象上不难理解而且容易工程上控制,因此得到了流行。但在一般角度上,这种方式有明显的固有缺陷。

首先,OOP 一向是强调状态可变的。典型的 OO 意义的对象通常具有若干个可变的非平凡(non-trivial) 状态——用 OOP 直接映射实现时,对应有若干属性允许被修改。

考虑 class-based OOP 内部的套路,使用属性会引发疑惑:为什么不用“方法(method) ”和“消息(message) ”的传递来表示明确的可能带有副作用的操作?

若修改属性具有固定的副作用且被修改的属性不共享状态,这里的意图尚且还容易被读懂;否则一旦缺乏文档,对程序的可理解性也是灾难。(当然,使用替代手法根本上也不能避免这样的问题,但好歹没有欺骗性地隐藏这些问题的存在性。)

用属性抽象修改操作的可行边界是有问题的。在更一般的 PL 和实用意义上,修改是副作用(side effect) ,意味着具体的访问顺序是明确必要的,访问操作之间不能随意调换。使用只读数据域实现的不共享状态以外情形的属性,往往是一种过强的约束而曲解需求。

而如果不那样做,“属性”比“数据域”多提供的,就只剩下访问控制这样的实现特性了。

保留这样的抽象在这里并不是一个合算的买卖:多出来的概念并没有什么普遍实用性。

即便要强调封装,也不应该依赖访问控制这样的具体方式——何况用属性实现类内部状态的封装这种方式如其它 class-OOP 惯用手段一样笼统地把接口设计的责任推给类的 API (而不是类的)设计者上,不利于建模和代码实现的分离,而变相鼓励更差的设计。

所以结论是,“属性”这个抽象的存在在大部分意义下都是冗余的,如果不是有害的话。

某些语言特性的构造

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 了还能刷回去,不是不可逆的。

其它待挂列表

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

实例:所谓的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

重定向

文章内容已转移到这里

提交的版本保持和这里之前更新的版本相同的内容。

贴吧历史资源整理

吧务操作

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

关于头衔的外吧评论

会员名

疑似卖吧事件

经过

2015-11-21 凌晨发现所有吧主(当时仅有菊长)和所有小吧主均被撤销

之后,吧内出现“贴吧合伙人”置顶公告,大概是长这样的。发贴人是贴吧认证小尾巴的用户 Ly包子先森 ,认证机构为南昌碉堡文化传媒有限公司

因为先前舰吧 70W 事件,吧主撤得又莫名其妙,期间普遍让人怀疑官方卖吧。(事后发现该公告也不止个别贴吧发布。另有在本吧没有发几贴的用户申请吧主,被怀疑是空降,但没有坐实。)发消息问了一下价格,似乎是(半年) 4W ,意外地便宜……

之间有各路贴吧吧友前来观光。有吧友指出先前数学吧和钢琴吧也有吧务被撤,管理转手的情况。不过所幸没几天事情平稳解决了。该公告后在 2015-11-26 被删除。最终等成功申请大吧成功上位并恢复小吧和吧内秩序。

评注

《百度贴吧吧主制度》(2016年第1版)第五节(商业官方吧吧主)第一零九条规定:“本制度上线前存在的官方吧吧主,贴吧官方可以无条件收回,重新供企业用户申请。”这完全不符合本事件,因为:

  • 这个制度是在事件后才出现,实属事后诸葛亮。
  • 即使无视生效时间,第一零五条规定,“商业官方吧(以下简称官方吧)是为企业建立的以品牌名、产品名、产品型号等命名的吧,官方吧具有与普通吧不同的功能、样式和机制策略。”本吧并不符合商业官方吧的主题要求。

可能正是因为这些原因,几天后这个事件就不了了之了。之后,疾病类贴吧的贴吧合伙人制度被下架。不久后的魏则西事件则把贴吧运营推向了舆论中心。不过贴吧始终没有放弃合伙人和各种其它形式的以盈利为目的的商业运营。到 2019 年 3 月 Bilibili吧吧务被警告及下台事件,仍有人怀疑贴吧官方在其中背地交易。

个人 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

(其实忘了重点是啥了,大约是得区分图形库和 UI 的罢……)

贴吧官方作死

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

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