你们会向新人推荐 C++么?

2016 年 8 月 18 日
 xiqingongzi
/t/300012 提问 C++ 的路线图,很多人都在劝我放弃
但我是起意学 C++ 一方面是个人需要,另一方面是我的一个堂叔推荐我学 C++的。

什么样的情况下你会推荐别人学 C++ 。什么情况下不推荐 C++?

我觉得我堂叔不至于坑我吧....
13265 次点击
所在节点    C
174 条回复
x8
2016 年 8 月 19 日
@FrankHB 你和我对学习这件事本身有不同的理解。

你认为一开始就应该给初学者提供比较完善的语言和类型系统,让初学者知道什么是对的,什么是先进的,以最快速度学习享受到现在最先进的理论成果,更多的关注语言本身的描述能力。

我认为一开始应该提供给初学者最大的自由,暴露合适的硬件细节,让初学者明白计算机到底在做什么。

我说的奢俭,是指初学者一旦高级语言用爽了,屏蔽了本来应该了解的细节,很可能就呆在舒适区不想出来了,称他还没养成惰性,照脸糊 C 。

我不明白学 C 的低效在哪,风险在哪。面向初学者的教材几乎全是 C ,算法教材也大部分是 C 代码或者类 C 伪码,各大高校开设的计算机课程必定有 C ,已经为初学者尽可能降低了学习曲线。

初学者需要“学习 C ”,而不是“精通 C ”,不要去研究 C 语言本身的细枝末节,最主要是理解指针概念。很多初学者学 C ,从来没搞清楚过指针运作原理,就转投其他高级语言,我认为是有害的。不尝试从底层去理解语言特性,学习新语言就真的是换一种语言,重新学习新语言的语法特性,以往的经验没法融会贯通。为了逃避指针而去学高级语言的趁早转行,因为对语言的理解也就止步于语义了,隔两年,潮流变了,语言换了,才是一脸懵逼。

有底层知识的支持,很容易就能理解函数指针,回调,委托的关系;从来就没有所谓的引用传递,都是值复制;缓存一致性问题不单单出现在底层,数据库,分布式系统同样出现,在底层同样能接触“速度不够就要加缓存,有缓存就要解决缓存一致性问题”这个朴素道理;类型系统都是纸老虎,是数据就都得放到内存里算。

为什么 C 语言最合适,往上看,高级语言写法风格大都类 C ,要学习其他语言并无太大的不适应。类型系统一个语言一套,但是指针是每个计算机都有的;往下看,完全可以通过逆向 C 程序来往更底层的方向走。如果这个初学者的毕生梦想是写更好更快更稳定的嵌入式系统内核以及周边驱动开发,那起手让他学其他高级语言,了解各种类型系统有什么意义。

如果是做语言研究,连用 C 入个门都困难,或者跌入个 C 设计上的坑都要大呼小叫,我觉得可能他的能力不适合做语言研究。语言设计也是填旧坑,挖新坑,不知旧坑,怎挖新坑?设计问题是权衡问题, C 语言是个不算好但是经典的范本。

C 语言肯定不是做工程或者做语言研究最佳的入门语言。我说的是:

在不清楚这个初学者以后到底要干什么的情况下,在初学者连自己都不知道自己到底要干什么的情况下, C 是性价比最高的语言。
frozenshadow
2016 年 8 月 19 日
@lizon 赞同。基础扎实了,其实学什么都很快
kaneyuki
2016 年 8 月 19 日
现在科班连 C/C++都学不动了么。。。时代变化快啊
kaneyuki
2016 年 8 月 19 日
从俭入奢易,由奢返俭难啊,同学
shyling
2016 年 8 月 19 日
私以为问这种问题的话还是不要学 c 艹的好
proudzhu
2016 年 8 月 19 日
@FrankHB 指针就是个地址,这么简单明了,哪来的什么抽象啊,当成 sum type 理解是什么鬼
FrankHB
2016 年 8 月 19 日
@lizon 你还是没找到点。

> 你认为一开始就应该给初学者提供比较完善的语言和类型系统,让初学者知道什么是对的,什么是先进的,以最快速度学习享受到现在最先进的理论成果,更多的关注语言本身的描述能力。

如果我认为“一开始就应该给初学者提供比较完善的语言和类型系统”,那么压根就不用提 C 了。
但是考虑实用,这种“比较完善”的东西并不存在,所以并不需要从这个角度考虑先从什么上手。
(不管实用的话,可以考虑 Racket ,但有些知识复用性比较差。)

> 我认为一开始应该提供给初学者最大的自由,暴露合适的硬件细节,让初学者明白计算机到底在做什么。

用 C 显然不是最大的自由,而且很容易让初学者搞不清计算机到底在做什么。
因为 C 暴露的硬件细节其实不多——至少没 C++多。而且经常暴露得很不地道。
例如考虑不相关指针比较是 UB 这点就可以明白实际上 C 自身的设计(强调可移植性)完全是和单一地址空间的简化实现假设相悖的,和 C++11/D 形成鲜明对比。
(其实理论上来讲这里 C 的设计更“正确”,只是实际上几乎并无卵用。)

注意,我猜你想要的“暴露硬件”就是这类语言设计蕴含的抽象假设。
如果是指操作硬件的接口,那么 C 无能为力。必须了解 C 以外的知识才能谈论是否能上手。这对初学者是不必要的负担。

> 我不明白学 C 的低效在哪,风险在哪。

低效有几方面的原因。
首先 C 涵盖的新手急需的知识点太少,学其它语言上手容易收获更多。
其次是教 C 的材料多数比较劣质,光是找到对路的参考资料就需要额外成本。
再次,即便教对了路, C 本身的一些古董劣质设计很容易造成先入为主的错误观念形成误导,阻碍进一步学习——除非有意忘掉一些东西——这就是低效。
核心风险是 C 没学好, C 以外的知识也没学好,另外还会教坏别的小朋友。
因为上面几个原因,拿 C 混搭基础课造成的麻烦比其它语言大得多。

> 面向初学者的教材几乎全是 C ,算法教材也大部分是 C 代码或者类 C 伪码,各大高校开设的计算机课程必定有 C ,已经为初学者尽可能降低了学习曲线。
这是错觉。问题包含几个方面:
不加前提地营造 C “够用”的假象,而不是在使用之前引导学生了解适用范围。
算法实现如果只是伪码描述目的还好,但似是而非的、和现实使用不怎么着边的实现风格就大有问题:这是在误导这类实现是“正常”的或者“不重要”的。
注意即便主题不是实现,作者仍然可以使它们接近实用的做法,而不是普遍地拿半成品搪塞。(当然并不是所有算法书都这么烂,但跟 C 沾边的主流如此。)

使用不容易自然过渡到实用场合的代码教学,使学习者最后至少要学会两套不太相干的写法才能体现出所学知识的用途,这显然不是“降低了学习曲线”,而是人为抬线。

> 初学者需要“学习 C ”,而不是“精通 C ”,不要去研究 C 语言本身的细枝末节,最主要是理解指针概念。

我说过了指针作为实现技巧是个小聪明,而作为语言特性是个烂发明。
如果你是指跟理解指针有助于和了解硬件,很遗憾,这个也是错觉。因为 C 的指针不是描述体系结构时使用的“指针”——有关联,但完全是两回事。
(不信,让这些自以为学会 C 的去看 ISA 手册,看看真正地道的“指针”和 C 的这玩意儿有多大出入,需要浪费多少额外时间才能明白自己想错了。)
另外,即便只是说 C ,我觉得不能理解为什么指针不靠谱的,不该视为理解了指针。具体地,真“搞清楚过指针运作原理”,除去程序语言理论专业知识,下面的一大半应该是能自己独自发现的:
https://github.com/FrankHB/pl-docs/blob/master/zh-CN/why-is-pointer-awful.md

> 有底层知识的支持,很容易就能理解函数指针,回调,委托的关系;从来就没有所谓的引用传递,都是值复制;缓存一致性问题不单单出现在底层,数据库,分布式系统同样出现,在底层同样能接触“速度不够就要加缓存,有缓存就要解决缓存一致性问题”这个朴素道理;类型系统都是纸老虎,是数据就都得放到内存里算。

我很确信依赖实现支持的理解会造成一些典型的错误理解。实例很多。比如被 const folding 坑了的。

你后面一些理解也是典型 yy ,但对大多数用户影响更大的是原则性方法论错误:模糊问题和解的边界。谁来替你保证实际的实现和你想象的一样?出问题谁负责?

顺便,函数指针不管在 C 和 C++中都是大坑,因为甚至连对应的“地址”是什么都扯不清楚。这种 spec 基础问题表明这些语言不适合入门。

> 为什么 C 语言最合适,往上看,高级语言写法风格大都类 C ,要学习其他语言并无太大的不适应。

如果是“都”,那么稍微有点用。然而“大都”是最差的情况——似是而非,想要正确就基本不能合并学习路径复用历史知识,鸡肋。

> 类型系统一个语言一套,但是指针是每个计算机都有的;往下看,完全可以通过逆向 C 程序来往更底层的方向走。

你搞反了,“没有”类型系统是类型论应用的简化特例。这个特例对设计语言的人有点用——因为可以按需定制自己需要的类型系统而不是被渣语言设计恶心;但对多数用户纯属浪费——至少使在具体任务中以不够系统的方式重复造不知所谓的轮子代替缺少的静态契约检查变得有必要了。

C 之所以在 B 上加上了类型系统,主要初衷也就是为了避免这里的不利影响。只不过由于设计者和实现技术的局限性,很不地道罢了。

至于“一定有”指针那还是错觉,你对体系结构设计的见识太少。至少可以确定 ISA native 的 pointer 不保证说的是一回事且可以以一致的方式使用。如果地址操作数直接够用,完全不需要引入 CISC 风格的指针概念。

逆向 C 程序是为了逆向本身的需求,如果说了解底层,比直接学习 ISA 慢不知哪去了……

> 如果是做语言研究,连用 C 入个门都困难,或者跌入个 C 设计上的坑都要大呼小叫,我觉得可能他的能力不适合做语言研究。

如果做语言研究,连 C 那么多明显的窟窿(以至于不适合入门)在哪都找不到,我觉得无论如何都是白费劲。

C 作为样本研究是有价值的,所以可以一学。仅此而已。

现在业界做语言和实现原型主要是 Scheme 这样的 Lisp 系方言。为了让之前学的 C 不浪费而故意复用 C 的知识也基本就是浪费时间,经验不够的还不如死了这条心。

> 在不清楚这个初学者以后到底要干什么的情况下,在初学者连自己都不知道自己到底要干什么的情况下, C 是性价比最高的语言。

我不清楚你有没有看到我指出的不管出于什么实用目的,基本上都有比直接学 C 更高效的路径这点。所以把 C 排在技能列表前面性价比不可能是最高的。
FrankHB
2016 年 8 月 19 日
@proudzhu 看来又是个学傻的…… C 的指针加法跟地址加法一回事?
嗯,我怀疑你根本就不懂 C 。
eliteYang
2016 年 8 月 19 日
学什么跟语言无关,而更多的是解决问题的能力,不过学了 C++之后想转其他语言相对容易一些,但是 C++也是非常难的,包括科班都不怎么学了,更多的新型语言能更快更好的解决实际问题,并且学习成本低,作为企业考虑,自然愿意选择这些成本低的技术,而且人也比较好招。

所以,学什么东西是看个人了,自己喜欢就去学,总归没害处的。
bramblex
2016 年 8 月 19 日
@proudzhu

指针就是地址不抽象只是对我们这些有良好计算机基础的人来说不抽象,那地址你要不要跟初学者讲清楚是什么?地址怎么讲? CPU / 内存 这些组成原理基本的东西要讲吧,数据和代码都是内存上的块要讲吧?这样子杂七杂八讲起来,一个指针上面卡上十天半个月不奇怪吧?
bramblex
2016 年 8 月 19 日
@lizon

你可能技术很牛逼,但是说实话,你对人性的把握真是差得一逼,找你教那就坑学生的。
proudzhu
2016 年 8 月 19 日
@FrankHB 不是一回事,那个是编译器的优化,难道你喜欢每次都加个 sizeof ?
一言不合就学傻,按你说的我需要先理解 sum type ,再理解编译器中 sum type 的具体实现,再来理解指针,绕一圈?一个本来相对底层的概念非得用个抽象的东西来理解?
我没通读过 c 标准,没写过编译器,你说我不懂我也没什么说的。
kaneyuki
2016 年 8 月 19 日
事实上我觉得某人答的和题主问的完全不是一回事儿
aoteman
2016 年 8 月 19 日
C 找工作是更麻烦事,老死机告诉你
proudzhu
2016 年 8 月 19 日
@bramblex 你要理解指针就必须要理解计算机体系结构,这是基础,想深入理解就不要想抄捷径。
kaneyuki
2016 年 8 月 19 日
@aoteman 用什么起手和用什么找工作也是两回事儿。。
x8
2016 年 8 月 19 日
@bramblex 有的人偏向于通过别人一步一步引导,以温和的方式来进行了解;有的人偏向于尽可能多的了解细节,以自由探索的方式来了解。我个人认为后一种品质更有利于自身发展,所以希望初学者一开始就培养自己这种品质。只是方式选择的问题,不分对错,我也只是发表了自己的见解。
只谈技术,不谈人性。
FrankHB
2016 年 8 月 19 日
@proudzhu ……看来你不仅没学明白 C ,也不懂什么叫“优化”。
对 C 来讲,允许编译器优化而不依赖语言扩展的唯一依据是不违背 observable behavior 和 abstract machine 指定的等价性( C++倒是在 as-if rule 以外还有 copy elision 和 operator new merging 之类的开洞)。不管 sizeof 还是指针的类型都是在语义规则里直接钦定的被 abstract machine 都支持的东西,编译器必须得正确地生成不同的代码(还有可能报错),什么时候有脸叫“优化”了?

> 一言不合就学傻,按你说的我需要先理解 sum type ,再理解编译器中 sum type 的具体实现,再来理解指针,绕一圈?一个本来相对底层的概念非得用个抽象的东西来理解?

这还真是学傻了。
1.sum type 在 C 的常见实现就是(untagged) union+表示 tag 的整数 /枚举值。这是很常用的惯用法,我不信真学会 C 的连这种做法都理解不了。也许只是不求甚解的时候不太理解计算机科学更通用的术语自己吓唬自己罢了。
2.上面提 sum type 是指代替指针这种错误的偷懒设计应该怎么办。注意 tag 在静态语言中是能编码为静态类型确定的,所以可以实现成某些 C++库中的 compress_pair 。 C 的类型系统太弱没法自定义数据类型来描述,如果用 C++,忽略 tag 以后运行时内部在某些方言中的表示可以大致是这样:
template<typename _Tp>
using __pointer<_Tp> =
union
{
__uintptr_t __address;
__optional<_Tp> __nullable_ref;
// ...
// ...一堆 friend 当 std::is_object<_Tp>()的时候定义 pointer arithmetic ,略。
} __attribute__((__may_be_aliased__));

另外,这里的关键问题你理解错了。正常点的语言设计根本就不应该在实现扩展外出现这种投机取巧的类型,更应提供给用户的是等价于这里内部表示的__uintptr_t 和__optional 等等的(高阶)类型。所以你压根就不需要理解这样的实现。
archxm
2016 年 8 月 19 日
一个个,都长篇大论的,你们真是在上班么?这样真是合格的程序员?
cdffh
2016 年 8 月 19 日
我在教小学生 c++. c++确实不简单 不过小学生都学的挺好的. 大家也不要过于夸大难度了.

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://v2ex.ih06.com/t/300102

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX