-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.html
5064 lines (3415 loc) · 529 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html class="theme-next mist use-motion" lang>
<head><meta name="generator" content="Hexo 3.8.0">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<link rel="stylesheet" href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2">
<link rel="stylesheet" href="/css/main.css?v=7.1.1">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=7.1.1">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=7.1.1">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=7.1.1">
<link rel="mask-icon" href="/images/logo.svg?v=7.1.1" color="#222">
<script id="hexo.configurations">
var NexT = window.NexT || {};
var CONFIG = {
root: '/',
scheme: 'Mist',
version: '7.1.1',
sidebar: {"position":"left","display":"post","offset":12,"onmobile":false,"dimmer":false},
back2top: true,
back2top_sidebar: false,
fancybox: false,
fastclick: false,
lazyload: false,
tabs: true,
motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
algolia: {
applicationID: '',
apiKey: '',
indexName: '',
hits: {"per_page":10},
labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
}
};
</script>
<meta property="og:type" content="website">
<meta property="og:title" content="DummyHead">
<meta property="og:url" content="https://alphaalgorithms.github.io/index.html">
<meta property="og:site_name" content="DummyHead">
<meta property="og:locale" content="default">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="DummyHead">
<link rel="canonical" href="https://alphaalgorithms.github.io/">
<script id="page.configurations">
CONFIG.page = {
sidebar: "",
};
</script>
<title>DummyHead</title>
<noscript>
<style>
.use-motion .motion-element,
.use-motion .brand,
.use-motion .menu-item,
.sidebar-inner,
.use-motion .post-block,
.use-motion .pagination,
.use-motion .comments,
.use-motion .post-header,
.use-motion .post-body,
.use-motion .collection-title { opacity: initial; }
.use-motion .logo,
.use-motion .site-title,
.use-motion .site-subtitle {
opacity: initial;
top: initial;
}
.use-motion .logo-line-before i { left: initial; }
.use-motion .logo-line-after i { right: initial; }
</style>
</noscript>
</head>
<body itemscope itemtype="http://schema.org/WebPage" lang="default">
<div class="container sidebar-position-left
page-home">
<div class="headband"></div>
<header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-wrapper">
<div class="site-meta">
<div class="custom-logo-site-title">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<span class="site-title">DummyHead</span>
<span class="logo-line-after"><i></i></span>
</a>
</div>
</div>
<div class="site-nav-toggle">
<button aria-label="Toggle navigation bar">
<span class="btn-bar"></span>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
</button>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="menu">
<li class="menu-item menu-item-home menu-item-active">
<a href="/" rel="section"><i class="menu-item-icon fa fa-fw fa-home"></i> <br>Home</a>
</li>
<li class="menu-item menu-item-tags">
<a href="/tags/" rel="section"><i class="menu-item-icon fa fa-fw fa-tags"></i> <br>Tags<span class="badge">30</span></a>
</li>
<li class="menu-item menu-item-categories">
<a href="/categories/" rel="section"><i class="menu-item-icon fa fa-fw fa-th"></i> <br>Categories<span class="badge">16</span></a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives/" rel="section"><i class="menu-item-icon fa fa-fw fa-archive"></i> <br>Archives<span class="badge">32</span></a>
</li>
</ul>
</nav>
</div>
</header>
<main id="main" class="main">
<div class="main-inner">
<div class="content-wrap">
<div id="content" class="content">
<section id="posts" class="posts-expand">
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="https://alphaalgorithms.github.io/2019/06/14/如何掌握所有语言/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Gabriel">
<meta itemprop="description" content>
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="DummyHead">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2019/06/14/如何掌握所有语言/" class="post-title-link" itemprop="url">如何掌握所有语言</a>
</h2>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2019-06-14 10:45:36 / Modified: 10:53:57" itemprop="dateCreated datePublished" datetime="2019-06-14T10:45:36+08:00">2019-06-14</time>
</span>
<span class="post-comments-count">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-comment-o"></i>
</span>
<span class="post-meta-item-text">Comments: </span>
<a href="/2019/06/14/如何掌握所有语言/#comments" itemprop="discussionUrl">
<span class="post-comments-count disqus-comment-count" data-disqus-identifier="2019/06/14/如何掌握所有语言/" itemprop="commentCount"></span>
</a>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>对的,我这里要讲的不是如何掌握一种程序语言,而是所有的……</p>
<blockquote>
<p>语言的特性总结得挺好, 虽然比较啰里啰嗦.</p>
</blockquote>
<p>很多编程初学者至今还在给我写信请教,问我该学习什么程序语言,怎么学习。由于我知道如何掌握“所有”的程序语言,总是感觉这种该学“一种”什么语言的问题比较低级,所以一直没来得及回复他们 :P 可是逐渐的,我发现原来不只是小白们有这个问题,就连美国大公司的很多资深工程师,其实也没搞明白。</p>
<p>今天我有动力了,想来统一回答一下这个搁置已久的“初级问题”。类似的话题貌似曾经写过,然而现在我想把它重新写一遍。因为在跟很多人交流之后,我对自己头脑中的(未转化为语言的)想法,有了更精准的表达。</p>
<p>如果你存在以下的种种困惑,那么这篇文章也许会对你有所帮助:</p>
<ol>
<li>你是编程初学者,不知道该选择什么程序语言来入门。</li>
<li>你是资深的程序员或者团队领导,对新出现的种种语言感到困惑,不知道该“投资”哪种语言。</li>
<li>你的团队为使用哪种程序语言争论不休,发生各种宗教斗争。</li>
<li>你追逐潮流采用了某种时髦的语言,结果两个月之后发现深陷泥潭,痛苦不堪……</li>
</ol>
<p>虽然我已经不再过问这些世事,然而无可置疑的现实是,程序语言仍然是很重要的话题,这个情况短时间内不会改变。程序员的岗位往往会要求熟悉某些语言,甚至某些奇葩的公司要求你“深入理解 OOP 或者 FP 设计模式”。对于在职的程序员s,程序语言至今仍然是可以争得面红耳赤的宗教话题。它的宗教性之强,以至于我在批评和调侃某些语言(比如 Go 语言)的时候,有些人会本能地以为我是另外一种语言(比如 Java)的粉丝。</p>
<p>显然我不可能是任何一种语言的粉丝,我甚至不是 Yin 语言的粉丝 ;) 对于任何从没见过的语言,我都是直接拿起来就用,而不需要经过学习的过程。看了这篇文章,也许你会明白我为什么可以达到这个效果。理解了这里面的东西,每个程序员都应该可以做到这一点。嗯,但愿吧。</p>
<h2 id="重视语言特性,而不是语言"><a href="#重视语言特性,而不是语言" class="headerlink" title="重视语言特性,而不是语言"></a>重视语言特性,而不是语言</h2><p>很多人在乎自己或者别人是否“会”某种语言,对“发明”了某种语言的人倍加崇拜,为各种语言的孰优孰劣争得面红耳赤。这些问题对于我来说都是不存在的。虽然我写文章批评过不少语言的缺陷,在实际工作中我却很少跟人争论这些。如果有其它人在我身边争论,我甚至会戴上耳机,都懒得听他们说什么 ;) 为什么呢?我发现归根结底的原因,是因为我重视的是“语言特性”,而不是整个的“语言”。我能用任何语言写出不错的代码,就算再糟糕的语言也差不了多少。</p>
<p>任何一种“语言”,都是各种“语言特性”的组合。打个比方吧,一个程序语言就像一台电脑。它的牌子可能叫“联想”,或者“IBM”,或者“Dell”,或者“苹果”。那么,你可以说苹果一定比 IBM 好吗?你不能。你得看看它里面装的是什么型号的处理器,有多少个核,主频多少,有多少 L1 cache,L2 cache……,有多少内存和硬盘,显示器分辨率有多大,显卡是什么 GPU,网卡速度,等等各种“配置”。有时候你还得看各个组件之间的兼容性。</p>
<p>这些配置对应到程序语言里面,就是所谓“语言特性”。举一些语言特性的例子:</p>
<ul>
<li>变量定义</li>
<li>算术运算</li>
<li>for 循环语句,while 循环语句</li>
<li>函数定义,函数调用</li>
<li>递归</li>
<li>静态类型系统</li>
<li>类型推导</li>
<li>lambda 函数</li>
<li>面向对象</li>
<li>垃圾回收</li>
<li>指针算术</li>
<li>goto 语句</li>
</ul>
<p>这些语言特性,就像你在选择一台电脑的时候,看它里面是什么配置。选电脑的时候,没有人会说 Dell 一定是最好的,他们只会说这个型号里面装的是 Intel 的 i7 处理器,这个比 i5 的好,DDR3 的内存 比 DDR2 的快这么多,SSD 比磁盘快很多,ATI 的显卡是垃圾…… 如此等等。</p>
<p>程序语言也是一样的道理。对于初学者来说,其实没必要纠结到底要先学哪一种语言,再学哪一种。曾经有人给我发信问这种问题,纠结了好几个星期,结果一个语言都还没开始学。有这纠结的时间,其实都可以把他纠结过的语言全部掌握了。</p>
<p>初学者往往不理解,每一种语言里面必然有一套“通用”的特性。比如变量,函数,整数和浮点数运算,等等。这些是每个通用程序语言里面都必须有的,一个都不能少。你只要通过“某种语言”学会了这些特性,掌握这些特性的根本概念,就能随时把这些知识应用到任何其它语言。你为此投入的时间基本不会浪费。所以初学者纠结要“先学哪种语言”,这种时间花的很不值得,还不如随便挑一个语言,跳进去。</p>
<p>如果你不能用一种语言里面的基本特性写出好的代码,那你换成另外一种语言也无济于事。你会写出一样差的代码。我经常看到有些人 Java 代码写得相当乱,相当糟糕,却骂 Java 不好,雄心勃勃要换用 Go 语言。这些人没有明白,是否能写出好的代码在于人,而不在于语言。如果你的心中没有清晰简单的思维模型,你用任何语言表述出来都是一堆乱麻。如果你 Java 代码写得很糟糕,那么你写 Go 语言代码也会一样糟糕,甚至更差。</p>
<p>很多初学者不了解,一个高明的程序员如果开始用一种新的程序语言,他往往不是去看这个语言的大部头手册或者书籍,而是先有一个需要解决的问题。手头有了问题,他可以用两分钟浏览一下这语言的手册,看看这语言大概长什么样。然后,他直接拿起一段例子代码来开始修改捣鼓,想法把这代码改成自己正想解决的问题。在这个简短的过程中,他很快的掌握了这个语言,并用它表达出心里的想法。</p>
<p>在这个过程中,随着需求的出现,他可<br>能会问这样的问题:</p>
<ul>
<li>这个语言的“变量定义”是什么语法,需要“声明类型”吗,还是可以用“类型推导”?</li>
<li>它的“类型”是什么语法?是否支持“泛型”?泛型的 “variance” 如何表达?</li>
<li>这个语言的“函数”是什么语法,“函数调用”是什么语法,可否使用“缺省参数”?</li>
<li>……</li>
</ul>
<p>注意到了吗?上面每一个引号里面的内容,都是一种语言特性(或者叫概念)。这些概念可以存在于任何的语言里面,虽然语法可能不一样,它们的本质都是一样的。比如,有些语言的参数类型写在变量前面,有些写在后面,有些中间隔了一个冒号,有些没有。</p>
<p>这些实际问题都是随着写实际的代码,解决手头的问题,自然而然带出来的,而不是一开头就抱着语言手册看得仔仔细细。因为掌握了语言特性的人都知道,自己需要的特性,在任何语言里面一定有对应的表达方式。如果没有直接的方式表达,那么一定有某种“绕过方式”。如果有直接的表达方式,那么它只是语法稍微有所不同而已。所以,他是带着问题找特性,就像查字典一样,而不是被淹没于大部头的手册里面,昏昏欲睡一个月才开始写代码。</p>
<p>掌握了通用的语言特性,剩下的就只剩某些语言“特有”的特性了。研究语言的人都知道,要设计出新的,好的,无害的特性,是非常困难的。所以一般说来,一种好的语言,它所特有的新特性,终究不会超过一两种。如果有个语言号称自己有超过 5 种新特性,那你就得小心了,因为它们带来的和可能不是优势,而是灾难!</p>
<p>同样的道理,最好的语言研究者,往往不是某种语言的设计者,而是某种关键语言特性的设计者(或者支持者)。举个例子,著名的计算机科学家 Dijkstra 就是“递归”的强烈支持者。现在的语言里面都有递归,然而你可能不知道,早期的程序语言是不支持递归的。直到 Dijkstra 强烈要求 Algol 60 委员会加入对递归的支持,这个局面才改变了。Tony Hoare 也是语言特性设计者。他设计了几个重要的语言特性,却没有设计过任何语言。另外大家不要忘了,有个语言专家叫王垠,他是早期 union type 的支持者和实现者,也是 checked exception 特性的支持者,他在自己的<a href="http://www.yinwang.org/blog-cn/2017/05/23/kotlin" target="_blank" rel="noopener">博文</a>里指出了 checked exception 和 union type 之间的关系 :P</p>
<p>很多人盲目的崇拜语言设计者,只要听到有人设计(或者美其民曰“发明”)了一个语言,就热血沸腾,佩服的五体投地。他们却没有理解,其实所有的程序语言,不过是像 Dell,联想一样的“组装机”。语言特性的设计者,才是像 Intel,AMD,ARM,Qualcomm 那样核心技术的创造者。</p>
<h2 id="合理的入门语言"><a href="#合理的入门语言" class="headerlink" title="合理的入门语言"></a>合理的入门语言</h2><p>所以初学者要想事半功倍,就应该从一种“合理”的,没有明显严重问题的语言出发,掌握最关键的语言特性,然后由此把这些概念应用到其它语言。哪些是合理的入门语言呢?我个人觉得这些语言都可以用来入门:</p>
<ul>
<li>Scheme</li>
<li>C</li>
<li>Java</li>
<li>Python</li>
<li>JavaScript</li>
</ul>
<p>那么相比之下,我不推荐用哪些语言入门呢?</p>
<ul>
<li>Shell</li>
<li>PowerShell</li>
<li>AWK</li>
<li>Perl</li>
<li>PHP</li>
<li>Basic</li>
<li>Go</li>
<li>Rust</li>
</ul>
<p>总的说来,你不应该使用所谓“<a href="http://www.yinwang.org/blog-cn/2013/03/29/scripting-language" target="_blank" rel="noopener">脚本语言</a>”作为入门语言,特别是那些源于早期 Unix 系统的脚本语言工具。PowerShell 虽然比 Unix 的 Shell 有所进步,然而它仍然没有摆脱脚本语言的根本问题——他们的设计者不知道他们自己在干什么 :P</p>
<p>采用脚本语言学编程,一个很严重的问题就是使得学习者抓不住关键。脚本语言往往把一些系统工具性质的东西(比如正则表达式,Web 概念)加入到语法里面,导致初学者为它们浪费太多时间,却没有理解编程最关键的概念:变量,函数,递归,类型……</p>
<p>不推荐 Go 语言的原因类似,虽然 Go 语言不算脚本语言,然而他的设计者显然不明白自己在干什么。所以使用 Go 语言来学编程,你不能专注于最关键,最好的语言特性。关于 Go 语言的各种毛病,你可以参考这篇<a href="http://www.yinwang.org/blog-cn/2014/04/18/golang" target="_blank" rel="noopener">文章</a>。</p>
<p>同样的,我不觉得 Rust 适合作为入门语言。Rust 花了太大精力来夸耀它的“新特性”,而这些新特性不但不是最关键的部分,而且很多是有问题的。初学者过早的关注这些特性,不仅学不会最关键的编程思想,而且可能误入歧途。关于 Rust 的一些问题,你可以参考这篇<a href="http://www.yinwang.org/blog-cn/2016/09/18/rust" target="_blank" rel="noopener">文章</a>。</p>
<h2 id="掌握关键语言特性,忽略次要特性"><a href="#掌握关键语言特性,忽略次要特性" class="headerlink" title="掌握关键语言特性,忽略次要特性"></a>掌握关键语言特性,忽略次要特性</h2><p>为了达到我之前提到的融会贯通,一通百通的效果,初学者应该专注于语言里面最关键的特性,而不是被次要的特性分心。</p>
<p>举个夸张点的例子。我发现很多编程培训班和野鸡大学的编程入门课,往往一来就教学生如何使用 printf 打印“Hello World!”,进而要他们记忆 printf 的各种“格式字符”的意义,要他们实现各种复杂格式的打印输出,甚至要求打印到文本文件里,然后再读出来……</p>
<p>可是殊不知,这种输出输入操作其实根本不算是语言的一部分,而且对于掌握编程的核心概念来说,都是次要的。有些人的 Java 课程进行了好几个星期,居然还在布置各种 printf 的作业。学生写出几百行的 printf,却不理解变量和函数是什么,甚至连算术语句和循环语句都不知道怎么用!这就是为什么很多初学者感觉编程很难,我连 <code>%d</code>,<code>%f</code>,<code>%.2f</code> 的含义都记不住,还怎么学编程!</p>
<p>然而这些野鸡大学的“教授”头衔是如此的洗脑,以至于被他们教过的学生(比如我女朋友)到我这里请教,居然骂我净教一些没用的东西,学了连 printf 的作业都没法完成 :P 你别跟我讲 for 循环,函数什么的了…… 可不可以等几个月,等我背熟了 printf 的用法再学那些啊?</p>
<p>所以你就发现一旦被差劲的老师教过,这个程序员基本就毁了。就算遇到好的老师,他们也很难纠正过来。</p>
<p>当然这是一个夸张的例子,因为 printf 根本不算是语言特性,但这个例子从同样的角度说明了次要肤浅的语言特性带来的问题。</p>
<p>这里举一些次要语言特性的例子:</p>
<ul>
<li>C 语言的语句块,如果里面只有一条语句,可以不打花括号。</li>
<li>Go 语言的函数参数类型如果一样可以合并在一起写,比如 <code>func foo(s string, x, y, z int, c bool) { ... }</code></li>
<li>Perl 把正则表达式作为语言的一种特殊语法</li>
<li>JavaScript 语句可以在某些时候省略句尾的分号</li>
<li>Haskell 和 ML 等语言的 <a href="http://www.yinwang.org/blog-cn/2013/04/02/currying" target="_blank" rel="noopener">currying</a></li>
</ul>
<h2 id="自己动手实现语言特性"><a href="#自己动手实现语言特性" class="headerlink" title="自己动手实现语言特性"></a>自己动手实现语言特性</h2><p>在基本学会了各种语言特性,能用它们来写代码之后,下一步的进阶就是去实现它们。只有实现了各种语言特性,你才能完全地拥有它们,成为它们的主人。否则你就只是它们的使用者,你会被语言的设计者牵着鼻子走。</p>
<p>有个大师说得好,完全理解一种语言最好的方法就是自己动手实现它,也就是自己写一个解释器来实现它的语义。但我觉得这句话应该稍微修改一下:完全理解一种“语言特性”最好的方法就是自己亲自实现它。</p>
<p>注意我在这里把“语言”改为了“语言特性”。你并不需要实现整个语言来达到这个目的,因为我们最终使用的是语言特性。只要你自己实现了一种语言特性,你就能理解这个特性在任何语言里的实现方式和用法。</p>
<p>举个例子,学习 SICP 的时候,大家都会亲自用 Scheme 实现一个面向对象系统。用 Scheme 实现的面向对象系统,跟 Java,C++,Python 之类的语言语法相去甚远,然而它却能帮助你理解任何这些 OOP 语言里面的“面向对象”这一概念,它甚至能帮助你理解各种面向对象实现的差异。</p>
<p>这种效果是你直接学习 OOP 语言得不到的,因为在学习 Java,C++,Python 之类语言的时候,你只是一个用户,而用 Scheme 自己动手实现了 OO 系统之后,你成为了一个创造者。</p>
<p>类似的特性还包括类型推导,类型检查,惰性求值,如此等等。我实现过几乎所有的语言特性,所以任何语言在我的面前,都是可以被任意拆卸组装的玩具,而不再是凌驾于我之上的神圣。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>写了这么多,重要的话重复三遍:语言特性,语言特性,语言特性,语言特性!不管是初学者还是资深程序员,应该专注于语言特性,而不是纠结于整个的“语言品牌”。只有这样才能达到融会贯通,拿起任何语言几乎立即就会用,并且写出高质量的代码。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="https://alphaalgorithms.github.io/2019/06/14/解谜计算机科学/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Gabriel">
<meta itemprop="description" content>
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="DummyHead">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2019/06/14/解谜计算机科学/" class="post-title-link" itemprop="url">解谜计算机科学</a>
</h2>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2019-06-14 10:19:11 / Modified: 10:26:14" itemprop="dateCreated datePublished" datetime="2019-06-14T10:19:11+08:00">2019-06-14</time>
</span>
<span class="post-comments-count">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-comment-o"></i>
</span>
<span class="post-meta-item-text">Comments: </span>
<a href="/2019/06/14/解谜计算机科学/#comments" itemprop="discussionUrl">
<span class="post-comments-count disqus-comment-count" data-disqus-identifier="2019/06/14/解谜计算机科学/" itemprop="commentCount"></span>
</a>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>要掌握一个学科的精髓,不能从细枝末节开始。人脑的能力很大程度上受限于信念。一个人不相信自己的时候,他就做不到本来可能的事。信心是很重要的,信心却容易被挫败。如果只见树木不见森林,人会失去信心,以为要到猴年马月才能掌握一个学科。</p>
<blockquote>
<p>精辟, 信心是万物之始.<br>将表达式纳入到考虑的范围. 计算图, 模型, 符号</p>
</blockquote>
<p>所以我们不从“树木”开始,而是引导读者一起来探索这背后的“森林”,把计算机科学最根本的概念用浅显的例子解释,让读者领会到它们的本质。把这些概念稍作发展,你就得到逐渐完整的把握。你一开头就掌握着整个学科,而且一直掌握着它,只不过增添更多细节而已。这就像画画,先勾勒出轮廓,一遍遍的增加细节,日臻完善,却不失去对大局的把握。</p>
<p>一般计算机专业的学生学了很多课程,可是直到毕业都没能回答一个基础问题:什么是计算?这一章会引导你去发现这个问题的答案。不要小看这基础的问题,它经常是解决现实问题的重要线索。世界上有太多不理解它的人,他们走了很多的弯路,掉进很多的坑,制造出过度复杂或者有漏洞的理论和技术。</p>
<p>接下来,我们就来理解几个关键的概念,由此接触到计算的本质。</p>
<h2 id="手指算术"><a href="#手指算术" class="headerlink" title="手指算术"></a>手指算术</h2><p>每个人都做过计算,只是大部分人都没有理解自己在做什么。回想一下幼儿园(大概四岁)的时候,妈妈问你:“帮我算一下,4+3 等于几?” 你掰了一会手指,回答:7。当你掰手指的时候,你自己就是一台简单的计算机。</p>
<p>不要小看了这手指算术,它蕴含着深刻的原理。计算机科学植根于这类非常简单的过程,而不是复杂的高等数学。</p>
<p>现在我们来回忆一下这个过程。这里应该有一段动画,但现阶段还没有。请你对每一步发挥一下想象力,增加点“画面感”。</p>
<ol>
<li>当妈妈问你“4+3 等于几”的时候,她是一个程序员,你是一台计算机。计算机得到程序员的输入:4,+,3。</li>
<li>听到妈妈的问题之后,你拿出两只手,左手伸出四个指头,右手伸l出三个指头。</li>
<li>接着你开始自己的计算过程。一根根地数那些竖起来的手指,每数一根你就把它弯下去,表示它已经被数过了。你念道:“1,2,3,4,5,6,7。”</li>
<li>现在已经没有手指伸着,所以你把最后数到的那个数作为答案:7!整个计算过程就结束了。</li>
</ol>
<h2 id="符号和模型"><a href="#符号和模型" class="headerlink" title="符号和模型"></a>符号和模型</h2><p>这里的幼儿园手指算术包含着深刻的哲学问题,现在我们来初步体会一下这个问题。</p>
<p>当妈妈说“帮我算 4+3”的时候,4,+,3,三个字符传到你耳朵里,它们都是符号(symbol)。符号是“表面”的东西:光是盯着“4”和“3”这两个阿拉伯数字的曲线,一个像旗子,一个像耳朵,你是不能做什么的。你需要先用脑子把它们转换成对应的“模型”(model)。这就是为什么你伸出两只手,一只手表示 4,另一只表示 3。</p>
<p>这两只手的手势是“可操作”的。比如,你把左手再多弯曲一个手指,它就变成“3”。你再伸开一根手指,它就变成“5”。所以手指是一个相当好的机械模型,它是可以动,可操作的。把符号“4”和“3”转换成手指模型之后,你就可以开始计算了。</p>
<p>你怎么知道“4”和“3”对应什么样的手指模型呢?因为妈妈以前教过你。十根手指,对应着 1 到 10 十个数。这就是为什么人都用十进制数做算术。</p>
<p>我们现在没必要深究这个问题。我只是提示你,分清“符号”和“模型”是重要的。</p>
<h2 id="计算图"><a href="#计算图" class="headerlink" title="计算图"></a>计算图</h2><p>在计算机领域,我们经常用一些抽象的图示来表达计算的过程,这样就能直观地看到信息的流动和转换。这种图示看起来是一些形状用箭头连接起来。我在这里把它叫做“计算图”。</p>
<p>对于以上的手指算术 <code>4 + 3</code>,我们可以用下图来表示它:</p>
<p><img src="http://www.yinwang.org/csbook-images/adder.png" alt="img"></p>
<p>图中的箭头表示信息的流动方向。说到“流动”,你可以想象一下水的流动。首先我们看到数字 4 和 3 流进了一个圆圈,圆圈里有一个“+”号。这个圆圈就是你,一个会做手指加法的小孩。妈妈给你两个数 4 和 3,你现在把它们加起来,得到 7 作为结果。</p>
<p>注意圆圈的输入和输出方向是由箭头决定的,我们可以根据需要调整那些箭头的位置,只要箭头的连接关系和方向不变就行。它们不一定都是从左到右,也可能从右到左或者从上到下,但“出入关系”都一样:4 和 3 进去,结果 7 出来。比如它还可以是这样:</p>
<p><img src="http://www.yinwang.org/csbook-images/adder-topdown.png" alt="img"></p>
<p>我们用带加号的圆圈表示一个“加法器”。顾名思义,加法器可以帮我们完成加法。在上个例子里,你就是一个加法器。我们也可以用其他装置作为加法器,比如一堆石头,一个算盘,某种电子线路…… 只要它能做加法就行。</p>
<p>具体要怎么做加法,就像你具体如何掰手指,很多时候我们是不关心的,我们只需要知道这个东西能做加法就行。圆圈把具体的加法操作给“抽象化”了,这个蓝色的圆圈可以代表很多种东西。抽象(abstraction)是计算机科学至关重要的思维方法,它帮助我们进行高层面的思考,而不为细节所累。</p>
<h2 id="表达式"><a href="#表达式" class="headerlink" title="表达式"></a>表达式</h2><p>计算机科学当然不止 4 + 3 这么简单,但它的基本元素确实是如此简单。我们可以创造出很复杂的系统,然而归根结底,它们只是在按某种顺序计算像 4 + 3 这样的东西。</p>
<p>4 + 3 是一个很简单的表达式(expression)。你也许没听说过“表达式”这个词,但我们先不去定义它。我们先来看一个稍微复杂一些的表达式:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">2 * (4 + 3)</span><br></pre></td></tr></table></figure>
<p>这个表达式比 <code>4 + 3</code> 多了一个运算,我们把它叫做“复合表达式”。这个表达式也可以用计算图来表示:</p>
<p><img src="http://www.yinwang.org/csbook-images/add-mult.png" alt="img"></p>
<p>你知道它为什么是这个样子吗?它表示的意思是,先计算 <code>4 + 3</code>,然后把结果(7)传送到一个“乘法器”,跟 2 相乘,得到最后的结果。那正好就是 <code>2 * (4 + 3)</code> 这个表达式的含义,它的结果应该是 14。</p>
<p>为什么要先计算 <code>4 + 3</code> 呢?因为当我们看到乘法器 <code>2 * ...</code> 的时候,其中一个输入(2)是已知的,而另外一个输入必须通过加法器的输出得到。加法器的结果是由 4 和 3 相加得到的,所以我们必须先计算 <code>4 + 3</code>,然后才能与 2 相乘。</p>
<p>小学的时候,你也许学过:“括号内的内容要先计算”。其实括号只是“符号层”的东西,它并不存在于计算图里面。我这里讲的“计算图”,其实才是本质的东西。数学的括号一类的东西,都只是表象,它们是符号或者叫“语法”。从某种意义上讲,计算图才是表达式的本质或者“模型”,而“2 * (4 + 3)”这串符号,只是对计算图的一种表示或者“编码”(coding)。</p>
<p>这里我们再次体会到了“符号”和“模型”的差别。符号是对模型的“表示”或者“编码”。我们必须从符号得到模型,才能进行操作。这种从符号到模型的转换过程,在计算机科学里叫做“语法分析”(parsing)。我们会在后面的章节理解这个过程。</p>
<p>我们现在来给表达式做一个初步的定义。这并不是完整的定义,但你应该试着理解这种定义的方式。稍后我们会逐渐补充这个定义,逐渐完善。</p>
<p>定义(表达式):<strong>表达式</strong>可以是如下几种东西。</p>
<ol>
<li>数字是一个表达式。比如 1,2,4,15,……</li>
<li>表达式 + 表达式。两个表达式相加,也是表达式。</li>
<li>表达式 - 表达式。两个表达式相减,也是表达式。</li>
<li>表达式 * 表达式。两个表达式相乘,也是表达式。</li>
<li>表达式 / 表达式。两个表达式相除,也是表达式。</li>
</ol>
<p>注意,由于我们之前讲过的符号和模型的差别,为了完全忠于我们的本质认识,这里的“表达式 + 表达式”虽然看起来是一串符号,它必须被想象成它所对应的模型。当你看到“表达式”的时候,你的脑子里应该浮现出它对应的计算图,而不是一串符号。这个计算图的画面大概是这个样子,其中左边的大方框里可以是任意两个表达式。</p>
<p><img src="http://www.yinwang.org/csbook-images/expression-graph.png" alt="img"></p>
<p>是不是感觉这个定义有点奇怪?因为在“表达式”的定义里,我们用到了“表达式”自己。这种定义叫做“递归定义”。所谓<strong>递归</strong>(recursion),就是在一个东西的定义里引用这个东西自己。看上去很奇怪,好像绕回去了一样。递归是一个重要的概念,我们会在将来深入理解它。</p>
<p>现在我们可以来验证一下,根据我们的定义,<code>2 * (4 + 3)</code> 确实是一个表达式:</p>
<ul>
<li>首先根据第一种形式,我们知道 4 是表达式,因为它是一个数字。3 也是表达式,因为它是一个数字。</li>
<li>所以 <code>4 + 3</code> 是表达式,因为 <code>+</code> 的左右都是表达式,它满足表达式定义的第二种形式。</li>
<li>所以 <code>2 * (4 + 3)</code> 是表达式,因为 <code>*</code> 的左右都是表达式,它满足表达式定义的第四种形式。</li>
</ul>
<h2 id="并行计算"><a href="#并行计算" class="headerlink" title="并行计算"></a>并行计算</h2><p>考虑这样一个表达式:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(4 + 3) * (1 + 2)</span><br></pre></td></tr></table></figure>
<p>它对应一个什么样的计算图呢?大概是这样:</p>
<p><img src="http://www.yinwang.org/csbook-images/parallel.png" alt="img"></p>
<p>如果妈妈只有你一个小孩,你应该如何用手指算出它的结果呢?你大概有两种办法。</p>
<p>第一种办法:先算出 4+3,结果是 7。然后算出 1+2,结果是 3。然后算 7*3,结果是 21。</p>
<p>第二种办法:先算出 1+2,结果是 3。然后算出 4+3,结果是 7。然后算 7*3,结果是 21。</p>
<p>注意到没有,你要么先算 4+3,要么先算 1+2,你不能同时算 4+3 和 1+2。为什么呢?因为你只有两只手,所以算 4+3 的时候你就没法算 1+2,反之也是这样。总之,你妈妈只有你一个加法器,所以一次只能做一个加法。</p>
<p>现在假设你还有一个妹妹,她跟你差不多年纪,她也会手指算术。妈妈现在就多了一些办法来计算这个表达式。她可以这样做:让你算 4+3,不等你算完,马上让妹妹算 1+2。等到你们的结果(7 和 3)都出来之后,让你或者妹妹算 7*3。</p>
<p>发现没有,在某一段时间之内,你和妹妹<em>同时</em>在做加法计算。这种时间上重叠的计算,叫做<strong>并行计算</strong>(parallel computing)。</p>
<p>你和妹妹同时计算,得到结果的速度可能会比你一个人算更快。如果你妈妈还有其它几个孩子,计算复杂的式子就可能快很多,这就是并行计算潜在的好处。所谓“潜在”的意思是,这种好处不一定会实现。比如,如果你的妹妹做手指算数的速度比你慢很多,你做完了 4+3,只好等着她慢慢的算 1+2。这也许比你自己依次算 4+3 和 1+2 还要慢。</p>
<p>即使妹妹做算术跟你一样快,这里还有个问题。你和妹妹算出结果 7 和 3 之后,得把结果传递给下一个计算 7*3 的那个人(也许是你,也许是你妹妹)。这种“通信”会带来时间的延迟,叫做“通信开销”。如果你们其中一个说话慢,这比起一个人来做计算可能还要慢。</p>
<p>如何根据计算单元能力的不同和通信开销的差异,来最大化计算的效率,降低需要的时间,就成为了并行计算领域研究的内容。并行计算虽然看起来是一个“博大精深”的领域,可是你如果理解了我这里说的那点东西,就很容易理解其余的内容。</p>
<h2 id="变量和赋值"><a href="#变量和赋值" class="headerlink" title="变量和赋值"></a>变量和赋值</h2><p>如果你有一个复杂的表达式,比如</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(5 - 3) * (4 + (2 * 3 - 5) * 6)</span><br></pre></td></tr></table></figure>
<p>由于它有比较多的嵌套,人的眼睛是难以看清楚的,它要表达的意义也会难懂。这时候,你希望可以用一些“名字”来代表中间结果,这样表达式就更容易理解。</p>
<p>打个比方,这就像你有一个亲戚,他是你妈妈的表姐的女儿的丈夫。你不想每次都称他“我妈妈的表姐的女儿的丈夫”,所以你就用他的名字“叮当”来指代他,一下子就简单了。</p>
<p>我们来看一个例子。之前的复合表达式</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">2 * (4 + 3)</span><br></pre></td></tr></table></figure>
<p>其实可以被转换为等价的,含有变量的代码:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> a = 4 + 3 // 变量 a 得到 4+3 的值</span><br><span class="line"> 2 * a // 代码块的值</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>其中 <code>a</code> 是一个名字。<code>a = 4 + 3</code> 是一个“赋值语句”,它的意思是:用 a 来代表 4 + 3 的值。这种名字,计算机术语叫做<strong>变量</strong>(variable)。</p>
<p>这段代码的意思可以简单地描述为:计算 <code>4 + 3</code>,把它的结果表示为 <code>a</code>,然后计算 <code>2 * a</code>作为最后的结果。</p>
<p>有些东西可能扰乱了你的视线。两根斜杠 <code>//</code> 后面一直到行末的文字叫做“注释”,是给人看的说明文字。它们对代码的逻辑不产生作用,执行的时候可以忽略。许多语言都有类似这种注释,它们可以帮助阅读的人,但是会被机器忽略。</p>
<p>这段代码执行过程会是这样:先计算 <code>4 + 3</code> 得到 7,用 <code>a</code> 记住这个中间结果 7。接着计算 <code>2 * a</code> ,也就是计算 <code>2 * 7</code>,所以最后结果是 14。很显然,这跟 <code>2 * (4 + 3)</code> 的结果是一样的。</p>
<p><code>a</code> 叫做一个变量,它是一个符号,可以用来代表任意的值。除了 <code>a</code>,你还有许多的选择,比如 b, c, d, x, y, foo, bar, u21… 只要它不会被误解成其它东西就行。</p>
<p>如果你觉得这里面的“神奇”成分太多,那我们现在来做更深一层的理解……</p>
<p>再看一遍上面的代码。这整片代码叫做一个“代码块”(block),或者叫一个“序列”(sequence)。这个代码块包括两条语句,分别是 <code>a = 4 + 3</code> 和 <code>2 * a</code>。代码块里的语句会从上到下依次执行。所以我们先执行 <code>a = 4 + 3</code>,然后执行 <code>2 * a</code>。</p>
<p>最后一条语句 <code>2 * a</code> 比较特别,它是这个代码块的“值”,也就是最后结果。之前的语句都是在为生成这个最后的值做准备。换句话说,这整个代码块的值就是 <code>2 * a</code> 的值。不光这个例子是这样,这是一个通用的原理:代码块的最后一条语句,总是这个代码块的值。</p>
<p>我们在代码块的前后加上花括号 <code>{...}</code> 进行标注,这样里面的语句就不会跟外面的代码混在一起。这两个花括号叫做“边界符”。我们今后会经常遇到代码块,它存在于几乎所有的程序语言里,只是语法稍有不同。比如有些语言可能用括号 <code>(...)</code> 或者 <code>BEGIN...END</code>来表示边界,而不是用花括号。</p>
<p>这片代码已经有点像常用的编程语言了,但我们暂时不把它具体化到某一种语言。我不想固化你的思维方式。在稍后的章节,我们会把这种抽象的表达法对应到几种常见的语言,这样一来你就能理解几乎所有的程序语言。</p>
<p>另外还有一点需要注意,同一个变量可以被多次赋值。它的值会随着赋值语句而改变。举个例子:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> a = 4 + 3</span><br><span class="line"> b = a</span><br><span class="line"> a = 2 * 5</span><br><span class="line"> c = a</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这段代码执行之后,<code>b</code> 的值是 7,而 <code>c</code> 的值是 10。你知道为什么吗?因为 <code>a = 4 + 3</code> 之后,a 的值是 7。<code>b = a</code> 使得 <code>b</code> 得到值 7。然后 <code>a = 2 * 5</code> 把 <code>a</code> 的值改变了,它现在是 10。所以 <code>c = a</code> 使得 <code>c</code> 得到 10。</p>
<p>对同一个变量多次赋值虽然是可以的,但通常来说这不是一种好的写法,它可能引起程序的混淆,应该尽量避免。只有当变量表示的“意义”相同的时候,你才应该对它重复赋值。</p>
<h2 id="编译"><a href="#编译" class="headerlink" title="编译"></a>编译</h2><p>一旦引入了变量,我们就可以不用复合表达式。因为你可以把任意复杂的复合表达式拆开成“单操作算术表达式”(像 4 + 3 这样的),使用一些变量记住中间结果,一步一步算下去,得到最后的结果。</p>
<p>举一个复杂点的例子,也就是这一节最开头的那个表达式:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(5 - 3) * (4 + (2 * 3 - 5) * 6)</span><br></pre></td></tr></table></figure>
<p>它可以被转化为一串语句:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> a = 2 * 3</span><br><span class="line"> b = a - 5</span><br><span class="line"> c = b * 6</span><br><span class="line"> d = 4 + c</span><br><span class="line"> e = 5 - 3</span><br><span class="line"> e * d</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>最后的表达式 <code>e * d</code>,算出来就是原来的表达式的值。你观察一下,是不是每个操作都非常简单,不包含嵌套的复合表达式?你可以自己验算一下,它确实算出跟原表达式一样的结果。</p>
<p>在这里,我们自己动手做了“编译器”(compiler)的工作。通常来说,编译器是一种程序,它的任务是把一片代码“翻译”成另外一种等价形式。这里我们没有写编译器,可是我们自己做了编译器的工作。我们手动地把一个嵌套的复合表达式,编译成了一系列的简单算术语句。</p>
<p>这些语句的结果与原来的表达式完全一致。这种保留原来语义的翻译过程,叫做<strong>编译</strong>(compile)。</p>
<p>我们为什么需要编译呢?原因有好几种。我不想在这里做完整的解释,但从这个例子我们可以看到,编译之后我们就不再需要复杂的嵌套表达式了。我们只需要设计很简单的,只会做单操作算术的机器,就可以算出复杂的嵌套的表达式。实际上最后这段代码已经非常接近现代处理器(CPU)的汇编代码(assembly)。我们只需要多加一些转换,它就可以变成机器指令。</p>
<p>我们暂时不写编译器,因为你还缺少一些必要的知识。这当然也不是编译技术的所有内容,它还包含另外一些东西。但从这一开头,你就已经初步理解了编译器是什么,你只需要在将来加深这种理解。</p>
<h2 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h2><p>到目前为止,我们做的计算都是在已知的数字之上,而在现实的计算中我们往往有一些未知数。比如我们想要表达一个“风扇控制器”,有了它之后,风扇的转速总是当前气温的两倍。这个“当前气温”就是一个未知数。</p>
<p>我们的“风扇控制器”必须要有一个“输入”(input),用于得到当前的温度 t,它是一个温度传感器的读数。它还要有一个输出,就是温度的两倍。</p>
<p>那么我们可以用这样的方式来表达我们的风扇控制器:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">t -> t*2</span><br></pre></td></tr></table></figure>
<p>不要把这想成任何一种程序语言,这只是我们自己的表达法。箭头 <code>-></code> 的左边表示输入,右边表示输出,够简单吧。</p>
<p>你可以把 <code>t</code> 想象成从温度传感器出来的一根电线,它连接到风扇控制器上,风扇控制器会把它的输入(t)乘以 2。这个画面像这个样子:</p>
<p><img src="http://www.yinwang.org/csbook-images/function1.png" alt="img"></p>
<p>我们谈论风扇控制器的时候,其实不关心它的输入是哪里来的,输出到哪里去。如果我们把温度传感器和风扇从画面里拿掉,就变成这个样子:</p>
<p><img src="http://www.yinwang.org/csbook-images/function2.jpg" alt="img"></p>
<p>这幅图才是你需要认真理解的函数的计算图。你发现了吗,这幅图画正好对应了之前的风扇控制器的符号表示:<code>t -> t*2</code>。看到符号就想象出画面,你就得到了符号背后的模型。</p>
<p>像 <code>t -> t*2</code> 这样具有未知数作为输入的构造,我们把它叫做函数(function)。其中 <code>t</code> 这个符号,叫做这个函数的参数。</p>
<h2 id="参数,变量和电线"><a href="#参数,变量和电线" class="headerlink" title="参数,变量和电线"></a>参数,变量和电线</h2><p>你可能发现了,函数的参数和我们之前了解的“变量”是很类似的,它们都是一个符号。之前我们用了 <code>a, b, c, d, e</code> 现在我们有一个 <code>t</code>,这些名字我们都是随便起的,只要它们不要重复就好。如果名字重复的话,可能会带来混淆和干扰。</p>
<p>其实参数和变量这两种概念不只是相似,它们的本质就是一样的。如果你深刻理解它们的相同本质,你的脑子就可以少记忆很多东西,而且它可能帮助你对代码做出一些有趣而有益的转化。在上一节你已经看到,我用“电线”作为比方来帮助你理解参数。你也可以用同样的方法来理解变量。</p>
<p>比如我们之前的变量 <code>a</code>:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> a = 4 + 3</span><br><span class="line"> 2 * a</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>它可以被想象成什么样的画面呢?</p>
<p><img src="http://www.yinwang.org/csbook-images/wire.png" alt="img"></p>
<p>我故意把箭头方向画成从右往左,这样它就更像上面的代码。从这个图画里,你也许可以看到变量 <code>a</code> 和风扇控制器图里的参数 <code>t</code>,其实没有任何本质差别。它们都表示一根电线,那根电线进入乘法器,将会被乘以 2,然后输出。如果你把这些都看成是电路,那么变量 <code>a</code> 和参数 <code>t</code> 都代表一根电线而已。</p>
<p>然后你还发现一个现象,那就是你可以把 <code>a</code> 这个名字换成任何其它名字(比如 <code>b</code>),而这幅图不会产生实质的改变。</p>
<p><img src="http://www.yinwang.org/csbook-images/rename.png" alt="img"></p>
<p>这说明什么问题呢?这说明以下的代码(把 <code>a</code> 换成了 <code>b)</code>跟之前的是等价的:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> b = 4 + 3</span><br><span class="line"> 2 * b</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>根据几乎一样的电线命名变化,你也可以对之前的函数得到一样的结论:<code>t -> t*2</code> 和 <code>u -> u*2</code>,和 <code>x -> x*2</code> 都是一回事。</p>
<p>名字是很重要的东西,但它们具体叫什么,对于机器并没有实质的意义,只要它们不要相互混淆就可以。但名字对于人是很重要的,因为人脑没有机器那么精确。不好的变量和参数名会导致代码难以理解,引起程序员的混乱和错误。所以通常说来,你需要给变量和参数起好的名字。</p>
<p>什么样的名字好呢?我会在后面集中讲解。</p>
<h2 id="有名字的函数"><a href="#有名字的函数" class="headerlink" title="有名字的函数"></a>有名字的函数</h2><p>既然变量可以代表“值”,那么一个自然的想法,就是让变量代表函数。所以就像我们可以写</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">a = 4 + 3</span><br></pre></td></tr></table></figure>
<p>我们似乎也应该可以写</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">f = t -> t*2</span><br></pre></td></tr></table></figure>
<p>对的,你可以这么做。<code>f = t->t*2</code> 还有一个更加传统的写法,就像数学里的函数写法:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">f(t) = t*2</span><br></pre></td></tr></table></figure>
<p>请仔细观察 <code>t</code> 的位置变化。我们在函数名字的右边写一对括号,在里面放上参数的名字。</p>
<p>注意,你不可以只写</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">f = t*2</span><br></pre></td></tr></table></figure>
<p>你必须明确的指出函数的参数是什么,否则你就不会明白函数定义里的 <code>t</code> 是什么东西。明确指出 <code>t</code> 是一个“输入”,你才会知道它是函数的输入,是一个未知数,<strong>而不是在函数外面定义的其它变量</strong>。</p>
<p>这个看似简单的道理,很多数学家都不明白,所以他们经常这样写书:</p>
<blockquote>
<p>有一个函数 y = x*2</p>
</blockquote>
<p>这是错误的,因为他没有明确指出“<code>x</code> 是函数 y 的参数”。如果这句话之前他们又定义过 <code>x</code>,你就会疑惑这是不是之前那个 <code>x</code>。很多人就是因为这些糊里糊涂的写法而看不懂数学书。这不怪他们,只怪数学家自己对于语言不严谨。</p>
<h2 id="函数调用"><a href="#函数调用" class="headerlink" title="函数调用"></a>函数调用</h2><p>有了函数,我们可以给它起名字,可是我们怎么使用它的值呢?</p>
<p>由于函数里面有未知数(参数),所以你必须告诉它这些未知数,它里面的代码才会执行,给你结果。比如之前的风扇控制器函数</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">f(t) = t*2</span><br></pre></td></tr></table></figure>
<p>它需要一个温度作为输入,才会给你一个输出。于是你就这样给它一个输入:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">f(2)</span><br></pre></td></tr></table></figure>
<p>你把输入写在函数名字后面的括号里。那么你就会得到输出:4。也就是说 <code>f(2)</code> 的值是 4。</p>
<p>如果你没有调用一个函数,函数体是不会被执行的。因为它不知道未知数是什么,所以什么事也做不了。那么我们定义函数的时候,比如</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">f(t) = t*2</span><br></pre></td></tr></table></figure>
<p>当看到这个定义的时候,机器应该做什么呢?它只是记录下:有这么一个函数,它的参数是 <code>t</code>,它需要计算 <code>t*2</code>,它的名字叫 <code>f</code>。但是机器不会立即计算 <code>t*2</code>,因为它不知道 <code>t</code> 是多少。</p>
<h2 id="分支"><a href="#分支" class="headerlink" title="分支"></a>分支</h2><p>直到现在,我们的代码都是从头到尾,闷头闷脑地执行,不问任何问题。我们缺少一种“问问题”的方法。比如,如果我想表达这样一个“食物选择器”:如果气温低于 22 度,就返回 “hotpot” 表示今天吃火锅,否则返回 “ice cream” 表示今天吃冰激凌。</p>
<p>我们可以把它图示如下:</p>
<p><img src="http://www.yinwang.org/csbook-images/branch.png" alt="img"></p>
<p>中间这种判断结构叫做“分支”(branching),它一般用菱形表示。为什么叫分支呢?你想象一下,代码就像一条小溪,平时它沿着一条路线流淌。当它遇到一个棱角分明的大石头,就分成两个支流,分开流淌。</p>
<p>我们的判断条件 <code>t < 22</code> 就像一块大石头,我们的“代码流”碰到它就会分开成两支,分别做不同的事情。跟溪流不同的是,这种分支不是随机的,而是根据条件来决定,而且分支之后只有一支继续执行,而另外一边不会被执行。</p>
<p>我们现在看到的都是图形化表示的模型,为了书写方便,现在我们要从符号的层面来表示这个模型。我们需要一种符号表示法来表达分支,我们把它叫做 <code>if</code>(如果)。我们的饮料选择器代码可以这样写:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">t -> if (t < 22) </span><br><span class="line"> {</span><br><span class="line"> "hotpot"</span><br><span class="line"> }</span><br><span class="line"> else </span><br><span class="line"> {</span><br><span class="line"> "ice cream"</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>它是一个函数,输入是一个温度。<code>if</code> 后面的括号里放我们的判断条件。后面接着条件成立时执行的代码块,然后是一个 <code>else</code>,然后是条件不成立时执行的代码。它说:如果温度低于 22 度,我们就吃火锅,否则就吃冰激凌。</p>
<p>其中的 <code>else</code> 是一个特殊的符号,它表示“否则”。看起来不知道为什么 <code>else</code> 要在那里?对的,它只是一个装饰品。我们已经有足够的表达力来分辨两个分支,不过有了 <code>else</code> 似乎更加好看一些。很多语言里面都有 else 这个标记词在那里,所以我也把它放在那里。</p>
<p>这只是一个最简单的例子,其实那两个代码块里面不止可以写一条语句。你可以有任意多的语句,就像这样:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">t -></span><br><span class="line">if (t < 22)</span><br><span class="line">{</span><br><span class="line"> a = 4 + 3</span><br><span class="line"> b = a * 2</span><br><span class="line"> "hotpot"</span><br><span class="line">}</span><br><span class="line">else</span><br><span class="line">{</span><br><span class="line"> x = "ice cream"</span><br><span class="line"> x</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这段代码和之前是等价的,你知道为什么吗?</p>
<h2 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h2><p>上面一节出现了一种我们之前没见过的东西,我为了简洁而没有介绍它。这两个分支的结果,也就是加上引号的 “hotpot” 和 “ice cream”,它们并不是数字,也不是其它语言构造,而是一种跟数字处于几乎同等地位的“数据类型”,叫做<strong>字符串</strong>(string)。字符串是我们在计算机里面表示人类语言的基本数据类型。</p>
<p>关于字符串,在这里我不想讲述更加细节的内容,我把对它的各种操作留到以后再讲,因为虽然字符串对于应用程序很重要,它却并不是计算机科学最关键最本质的内容。</p>
<p>很多计算机书籍一开头就讲很多对字符串的操作,导致初学者费很大功夫去做很多打印字符串的练习,结果几个星期之后还没学到“函数”之类最根本的概念。这是非常可惜的。</p>
<h2 id="布尔值"><a href="#布尔值" class="headerlink" title="布尔值"></a>布尔值</h2><p>我们之前的 <code>if</code> 语句的条件 <code>t < 22</code> 其实也是一个表达式,它叫做“布尔表达式”。你可以把小于号 <code><</code> 看成是跟加法一类的“操作符”。它的输入是两个数值,输出是一个“布尔值”。什么是布尔值呢?布尔值只有两个:true 和 false,也就是“真”和“假”。</p>
<p>举个例子,如果 <code>t</code> 的值是 15,那么 <code>t < 22</code> 是成立的,那么它的值就是 true。如果 <code>t</code> 的值是 23,那么 <code>t < 22</code> 就不成立,那么它的值就是 false。是不是很好理解呢?</p>
<p>我们为什么需要“布尔值”这种东西呢?因为它的存在可以简化我们的思维。对于布尔值也有一些操作,这个我也不在这一章赘述,放到以后细讲。</p>
<h2 id="计算的要素"><a href="#计算的要素" class="headerlink" title="计算的要素"></a>计算的要素</h2><p>好了,现在你已经掌握了计算机科学的几乎所有基本要素。每一个编程语言都包括这些构造:</p>
<ol>
<li>基础的数值。比如整数,字符串,布尔值等。(基础的数据类型, 不属于语言的范畴</li>
<li>表达式。包括基本的算术表达式,嵌套的表达式。()</li>
<li>变量和赋值语句。</li>
<li>分支语句。</li>
<li>函数和函数调用。<br>(变量, 函数, 控制豫剧 ,表达式)</li>
</ol>
<p>你也许可以感觉到,我是把这些构造按照“从小到大”的顺序排列的。这也许可以帮助你的理解。</p>
<p>现在你可以回想一下你对它们的印象。每当学习一种新的语言或者系统,你只需要在里面找到对应的构造,而不需要从头学习。这就是掌握所有程序语言的秘诀。这就像学开车一样,一旦你掌握了油门,刹车,换挡器,方向盘,速度表的功能和用法,你就学会了开所有的汽车,不管它是什么型号的汽车。</p>
<p>我们在这一章不仅理解了这些要素,而且为它们定义了一种我们自己的“语言”。显然这个语言只能在我们的头脑里运行,因为我们没有实现这个语言的系统。在后面的章节,我会逐渐的把我们这种语言映射到现有的多种语言里面,然后你就能掌握这些语言了。</p>
<p>但是请不要以为掌握了语言就学会了编程或者学会了计算机科学。掌握语言就像学会了各种汽车部件的工作原理。几分钟之内,初学者就能让车子移动,转弯,停止。可是完了之后你还需要学习交通规则,你需要许许多多的实战练习和经验,掌握各种复杂情况下的策略,才能成为一个合格的驾驶员。如果你想成为赛车手,那就还需要很多倍的努力。</p>
<p>但是请不要被我这些话吓到了,你没有那么多的竞争者。现在的情况是,世界上就没有很多合格的计算机科学驾驶员,更不要说把车开得流畅的赛车手。绝大部分的“程序员”连最基本的引擎,油门,刹车,方向盘的工作原理都不明白,思维方式就不对,所以根本没法独自上路,一上路就出车祸。很多人把过错归结在自己的车身上,以为换一辆车马上就能成为好的驾驶员。这是一种世界范围的计算机教育的失败。</p>
<p>在后面的章节,我会引导你成为一个合格的驾驶员,随便拿一辆车就能开好。</p>
<h2 id="什么是计算"><a href="#什么是计算" class="headerlink" title="什么是计算"></a>什么是计算</h2><p>现在你掌握了计算所需要的基本元素,可是什么是计算呢?我好像仍然没有告诉你。这是一个很哲学的问题,不同的人可能会告诉你不同的结果。我试图从最广义的角度来告诉你这个问题的答案。</p>
<p>当你小时候用手指算 <code>4+3</code>,那是计算。如果后来你学会了打算盘,你用算盘算 4+3,那也是计算。后来你从我这里学到了表达式,变量,函数,调用,分支语句…… 在每一新的构造加入的过程中,你都在了解不同的计算。</p>
<p>所以从最广义来讲,计算就是“机械化的信息处理”。所谓机械化,你可以用手指算,可以用算盘,可以用计算器,或者计算机。这些机器里面可以有代码,也可以没有代码,全是电子线路,甚至可以是生物活动或者化学反应。不同的机器也可以有不同的计算功能,不同的速度和性能……</p>
<p>有这么多种计算的事实不免让人困惑,总害怕少了点什么,其实你可以安心。如果你掌握了上一节的“计算要素”,那么你就掌握了几乎所有类型的计算系统所需要的东西。你在后面所需要做的只是加深这种理解,并且把它“对应”到现实世界遇到的各种计算机器里面。</p>
<p>为什么你可以相信计算机科学的精华就只有这些呢?因为计算就是处理信息,信息有它诞生的位置(输入设备,固定数值),它传输的方式(赋值,函数调用,返回值),它被查看的地方(分支)。你想不出对于信息还有什么其它的操作,所以你就很安心的相信了,这就是计算机科学这种“棋类游戏”的全部规则。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="https://alphaalgorithms.github.io/2019/06/13/work-with-source-code/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Gabriel">
<meta itemprop="description" content>
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="DummyHead">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2019/06/13/work-with-source-code/" class="post-title-link" itemprop="url">14 Working with Source Code</a>
</h2>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2019-06-13 15:31:00" itemprop="dateCreated datePublished" datetime="2019-06-13T15:31:00+08:00">2019-06-13</time>
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-calendar-check-o"></i>
</span>
<span class="post-meta-item-text">Edited on</span>
<time title="Modified: 2019-06-15 14:46:20" itemprop="dateModified" datetime="2019-06-15T14:46:20+08:00">2019-06-15</time>
</span>
<span class="post-comments-count">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-comment-o"></i>
</span>
<span class="post-meta-item-text">Comments: </span>