函数式编程术语解析

函数式编程蔚然成风,越来越多的开源项目、技术交流在使用函数式编程的术语降低开发或沟通成本,这无形中对不了解函数式编程的开发者造成了一定的学习门槛,翻译本文的初衷就是要普及函数式编程的基本知识,从新的角度扩展编程思维。至于为什么要使用 JavaScript 演示函数式编程,一方面是因为 JavaScript 的特性在很多方面与函数式编程浑然天成,另一方面是因为 JavaScript 是世界上最 XX 的语言……

Arity

指函数的参数数量,由 -ary 和 -ity 这两个英文后缀拼接而成:

const sum = (a, b) => a + b;const arity = sum.length;console.log(arity); // => 2

Higher-Order Functions

高阶函数,此类函数可以接收其他函数作为参数,也可以返回一个函数作为返回值:

const filter = (pred, xs) => {   const result = [];   for (let idx = 0; idx < xs.length; idx++) {       if (pred(xs[idx])) {            result.push(xs[idx]);        }    }   return result;};const is = (type) => (x) => Object(x) instanceof type;filter(is(Number), [0, ‘1’, 2, null]); // => [0, 2]

Partial Application

偏函数,在原函数的基础上预填充(pre-filling)部分参数并返回的新函数:

// 下面是一个创建偏函数的辅助函数const partial = (f, …args) => (…moreArgs) => f(…args, …moreArgs);const add3 = (a, b, c) => a + b + c;// 预填充 (add3, 2, 3) 三个参数,空置最后一个参数,返回一个新的函数const fivePlus = partial(add3, 2, 3); // (c) => 2 + 3 + cfivePlus(4); // => 9

JavaScript 中的 Function.prototype.bind() 函数是创建偏函数的最简单方式:

const add1More = add3.bind(null, 2, 3); // => (c) => 2 + 3 + c

Currying

柯里化,将一个接收多个参数的函数转化为单参数函数的方式,转化后的函数每次只接收一个参数,然后返回一个新函数,新函数可以继续接收参数,直到接收到所有的参数:

const sum = (a, b) => a + b;sum(2, 3)// => 6const curriedSum = (a) => (b) => a + b;curriedSum(40)(2) // => 42.const add2 = curriedSum(2); // (b) => 2 + badd2(10) // => 12

Function Composition

函数合成,接收多个函数作为参数并返回一个新函数的方式,新函数按照传入的参数顺序,从右往左依次执行,前一个函数的返回值是后一个函数的输入值:

const compose = (f, g) => (a) => f(g(a))const floorAndToString = compose((val) => val.toString(), Math.floor)floorAndToString(121.212121) // => “121”

Purity

一个纯函数需要满足两个条件,第一是函数的返回值只能由输入值(函数接收的参数)决定,也就是说纯函数接收相同的参数会返回相同的值;第二是纯函数不会对自身作用域之外的运行环境产生副作用(side effects),比如说不会改变外部环境中变量的值,这会被认为是不安全的行为:

let greeting;const greet = () => greeting = “Hi, ” + window.name;// greet() 执行时更改了外部环境的变量greet(); // => “Hi, Brianne”

纯函数示例:

const greet = (name) => “Hi, ” + name ;greet(“Brianne”) // => “Hi, Brianne”

Side effects

如果函数或表达式与其自身作用域之外的可变数据(mutable data)发生了读写操作,那么此时函数和表达式就产生了副作用:

let greeting;const greet = () => greeting = “Hi, ” + window.name;// greet() 执行时更改了外部环境的变量greet(); // => “Hi, Brianne”// new Date() 是可变数据const differentEveryTime = new Date();// 这里表示系统接收到的输入值是不确定的,是一种可变数据console.log(“IO is a side effect!”);

Idempotent

幂等,同一个函数使用相同的参数嵌套执行多次的结果与执行一次的结果相同:

$$f(…f(f(x))…)=f(x)$$

Math.abs(Math.abs(10))sort(sort(sort([2,1])))

Point-Free Style

point-free style 是一种不显式向函数传递参数的代码风格,通常需要柯里化和高阶函数来实现:

const map = (fn) => (list) => list.map(fn);const add = (a) => (b) => a + b;// Not points-free// numbers 是一个显式传递的参数const incrementAll = (numbers) => map(add(1))(numbers);// Points-free// add(1) 的返回值隐式传递给了 map,作为 map 的 list 参数const incrementAll2 = map(add(1));

point-free style 的函数看起来就像是一个赋值表达式,没有使用我们常见的 function 或 => 等来声明其接收的参数。

Predicate

断言,一个返回布尔值的函数:

const predicate = (a) => a > 2;[1, 2, 3, 4].filter(predicate); // => [3, 4]

Contracts

TODO

Guarded Functions

TODO

Categories

categories 内部都绑定了具体的函数用于约束或执行特定的逻辑,比如 Monoid。

Value

任何可以赋值给变量的值都可以称为 value:

5Object.freeze({name: ‘John’, age: 30}) // The `freeze` function enforces immutability.(a) => a[1]undefined

Constant

常量,初始化后不能再次执行赋值操作的数据类型:

const five = 5const john = { name: ‘John’, age: 30 }// 因为常量不可变,所以下面表达式一定为 truejohn.age + five === ({ name: ‘John’, age: 30 }).age + (5)

常量具有 referentially transparent 的特性,也就是说将程序中出现的常量替换为它们实际的值,并不会影响程序的结果。译者话外:实际上在 JavaScript 中的 const 所声明的常量并不是完全稳定的,使用 Immutable.js 演示更加恰当:

const five = fromJS(5);const john = fromJS({name: ‘John’, age: 30})john.get(‘age’) + five === ({ name: ‘John’, age: 30 }).age + (5)

f(g()) === g

Functor

functor 都拥有 map 函数,并且在执行 map 之后会返回一个新的 functor:

object.map(x => x) === objectobject.map(x => f(g(x))) === object.map(g).map(f)

JavaScript 中最常见的 functor 就是数组类型的实例:

[1, 2, 3].map(x => x); // => [1, 2, 3]const f = x => x + 1;const g = x => x * 2;[1, 2, 3].map(x => f(g(x))); // => [3, 5, 7][1, 2, 3].map(g).map(f);     // => [3, 5, 7]

Pointed Functor

pointed functor 都拥有 of 函数,用于接收和构建 functor。ES2015 提供了 Array.of 函数,所以数组实例就可以看成是 pointed functor:

Array.of(1) // => [1]

Lift

lift 发生在你将值放入 functor 的时候,如果你将函数 lift 进了 Applicative Functor,那么就可以使用这个函数处理传递给这个 functor 的值。某些 lift 的实现拥有 lift 或 liftA2 函数,便于在 functor 上执行相关的函数:

const mult = (a, b) => a * b;const liftedMult = lift(mult); // => this function now works on functors like arrayliftedMult([1, 2], [3]); // => [3, 6]lift((a, b) => a + b)([1, 2], [3, 4]); // => [4, 5, 5, 6]

lift 一个单参数的函数非常类似于 map 操作:

const increment = (x) => x + 1;lift(increment)([2]); // => [3][2].map(increment); // => [3]

Referential Transparency

如果一个表达式可以被替换为实际的值而不影响程序的运行结果,那么我们就说这个表达式是 referentially transparent:

const greet = () => “Hello World!”;

以上面代码为例,任何调用 greet() 的地方都可以替换为 “Hello World!” 而不影响程序的执行结果。

Equational Reasoning

如果一个应用由多个表达式组合而成,且每个表达式都没有 side effect,那么这个应用就可以由部分推导出整体。

Lambda

匿名函数,本质上是一个 value:

function(a){   return a + 1;};(a) => a + 1;// Lambda 常用语高阶函数中[1, 2].map((a) => a + 1); // = [2, 3]// Lambda 作为 value 被赋值给变量let addOne = (a) => a + 1;

Lambda Calculus

数学的分支之一,使用函数创建通用的计算模型(universal model of computation)。

Lazy evaluation

惰性求值,是一种按需执行的求值策略,只有需要某个值时才会执行相关的表达式。在函数式编程语言中,这一特性可用于构造无限列表。

const rand = function*() {   while (true) {       yield Math.random();    }}const randIter = rand();randIter.next().value; // 每次执行 next() 函数都会返回一个新的随机数// 有且只有在执行 next() 的时候才会返回新值

Monoid

Monoid,通过一个函数“合并”两个同类型数据后返回相同的数据类型。最简单的 monoid 就是两数相加:

1 + 1; // => 2

这里的 + 就是上面所说的“合并”函数。Monoid 中存在恒等式的概念:

1 + 0// => 1// 这里的 0 就是恒等式// Monoid 还必须满足结合律1 + (2 + 3) === (1 + 2) + 3; // => true// 数组的 concat() 操作可以构造一个 monoid[1, 2].concat([3, 4]); // => [1, 2, 3, 4]// 空数组可以视为是恒等式[1, 2].concat([]); // => [1, 2]

如果知道了一个函数的的恒等式和“合并”函数 compose,函数本身就是一个 monoid:

const identity = (a) => a;const compose = (f, g) => (x) => f(g(x));compose(foo, identity) ≍ compose(identity, foo) ≍ foo

Monad

Monad,是一个拥有 of 和 chain 函数的数据类型,chain 类似于 map,但它会输出非嵌套形式的结果:

[‘cat,dog’, ‘fish,bird’].chain((a) => a.split(‘,’)) // => [‘cat’, ‘dog’, ‘fish’, ‘bird’][‘cat,dog’, ‘fish,bird’].map((a) => a.split(‘,’)) // => [[‘cat’, ‘dog’], [‘fish’, ‘bird’]]

在其他函数式编程语言中,of 也被称为 return,chain 也被称为 flatmap 和 bind。

Comonad

Comonad,拥有 extract 和 extend 函数的数据类型:

const CoIdentity = (v) => ({   val: v,    extract() { return this.val },    extend(f) { return CoIdentity(f(this)) }})// extract() 可以从 functor 中取值CoIdentity(1).extract() // => 1// extend() 可以返回新的 comonadCoIdentity(1).extend(co => co.extract() + 1) // => CoIdentity(2)

Applicative Functor

Applicative Functor,是拥有 ap 函数的数据类型,ap 函数可以将 functor 中的值转化为其他 functor 中的同类型值:

[(a) => a + 1].ap([1]) // => [2]

这一特性对于多个 applicative functor 需要接收多个参数时,就显得很有用:

const arg1 = [1, 2];const arg2 = [3, 4];const add = (x) => (y) => x + y;const partiallyAppliedAdds = [add].ap(arg1); // => [(y) => 1 + y, (y) => 2 + y]partiallyAppliedAdds.ap(arg2); // => [4, 5, 5, 6]

Morphism

态射,一个转换函数。

Isomorphism

同构转换,相同数据下不同结构之间的转换。举例来说,2D 坐标既可以存储为数组 [2, 3] 也可以存储为 { x: 2, y: 3 }:

const pairToCoords = (pair) => ({x: pair[0], y: pair[1]})const coordsToPair = (coords) => [coords.x, coords.y]coordsToPair(pairToCoords([1, 2])) // => [1, 2]pairToCoords(coordsToPair({x: 1, y: 2})) // => { x: 1, y: 2 }

Setoid

Setoid,拥有 equals 函数的数据类型,可用于与其他同类型的数据进行比较。为 Array 类型添加 equals 函数使其成为 Setoid:

Array.prototype.equals = (arr) => {   const len = this.length   if (len !== arr.length) {       return false    }   for (let i = 0; i < len; i++) {       if (this[i] !== arr[i]) {           return false        }    }   return true}[1, 2].equals([1, 2]) // => true[1, 2].equals([0]) // => false

Semigroup

Semigroup,拥有 concat 函数的数据类型,可以与同类型数据进行合并:

[1].concat([2]) // => [1, 2]

Foldable

Foldable,拥有 reduce 函数的数据类型,可以将 Foldable 的实例转换为其他数据类型:

const sum = (list) => list.reduce((acc, val) => acc + val, 0);sum([1, 2, 3]) // => 6

Traversable

TODO

Type Signatures

类型签名,在 JavaScript 中通常会在注释中写明当前函数的参数类型和返回值类型,虽然各种语言的类型签名不同,但通常与以下示例相似:

// functionName :: firstArgType -> secondArgType -> returnType// add :: Number -> Number -> Numberconst add = (x) => (y) => x + y// increment :: Number -> Numberconst increment = (x) => x + 1

如果某个函数要作为参数传递给其他函数,那么在类型签名中需要使用括号包裹起这个函数的类型信息:

// call :: (a -> b) -> a -> bconst call = (f) => (x) => f(x)

上面示例中的 a、b 表示参数可以是任何数据类型的,但在下面的代码中,map 的类型签名表示: f 是一个函数,f 接收一个 a 类型的参数,返回一个 b 类型的值,同时 map 是一个柯里化的函数,其第二个接收一个列表形式的 a 类型参数,并返回列表形式的 b 类型参数:

// map :: (a -> b) -> [a] -> [b]const map = (f) => (list) => list.map(f)

Union type

联合类型,表示将多个类型信息放入一个类型变量中。JavaScript 中没有类型机制,所以让我们假设有一个类型变量 NumOrString,它表示 Number 或者 String 类型。+ 运算符在 JavaScript 中既可用于 Number,也可用于 String,所以我们使用 NumOrString 定义 + 的输入输出类型信息:

// add :: (NumOrString, NumOrString) -> NumOrStringconst add = (a, b) => a + b;add(1, 2); // => Number 3add(‘Foo’, 2); // => String “Foo2″add(‘Foo’, ‘Bar’); // => String “FooBar”

Product type

product type 同样包含多种基本类型:

// point :: (Number, Number) -> {x: Number, y: Number}const point = (x, y) => ({x: x, y: y});

Option

Option,是 union type 的特例,它只包含两种类型 Some 和 None。Option 常用于表示那些不确定是否返回值的函数:

// Naive definitionconst Some = (v) => ({   val: v,    map(f) {       return Some(f(this.val));    },    chain(f) {       return f(this.val);    }});const None = () => ({    map(f){       return this;    },    chain(f){       return this;    }});// maybeProp :: (String, {a}) -> Option aconst maybeProp = (key, obj) => typeof obj[key] === ‘undefined’ ? None() : Some(obj[key]);

使用 chain 函数执行链式调用可以返回具体的 Option:

// getItem :: Cart -> Option CartItemconst getItem = (cart) => maybeProp(‘item’, cart);// getPrice :: Item -> Option Numberconst getPrice = (item) => maybeProp(‘price’, item);// getNestedPrice :: cart -> Option aconst getNestedPrice = (cart) => getItem(obj).chain(getPrice);getNestedPrice({}); // => None()getNestedPrice({item: {foo: 1}}); // => None()getNestedPrice({item: {price: 9.99}}); // => Some(9.99)

某些语言中使用 Maybe 表示 Option,使用 Just 表示 Some,使用 Nothing 表示 Node。

查看文章…

5G入门科普

 

一个简单且神奇的公式

今天的故事,从一个公式开始讲起。

这是一个既简单又神奇的公式。说它简单,是因为它一共只有3个字母。而说它神奇,是因为这个公式蕴含了博大精深的通信技术奥秘,这个星球上有无数的人都在为之魂牵梦绕。

这个公式,就是它——

我相信很多同学都认出这个公式了,如果没认出来,而且你又是一个理科生的话,请记得有空多给你的中学物理老师打打电话!

小枣君解释一下,上面这个公式,这是物理学的基本公式,光速=波长×频率。

对于这个公式,可以这么说:无论是1G、2G、3G,还是4G、5G,万变不离其宗,全部都是在它身上做文章,没有跳出它的“五指山”。

且听我慢慢道来。。。

有线?无线?

通信技术,无论什么黑科技白科技,归根到底,就分为两种——有线通信和无线通信。

我和你打电话,信息数据要么在空中传播(看不见、摸不着),要么在实物上传播(看得见、摸得着)。

如果是在实体物质上传播,就是有线通信,基本上就是用的铜线、光纤这些线缆,统称为有线介质。

在有线介质上传播数据,速率可以达到很高的数值。

以光纤为例,在实验室中,单条光纤最大速度已达到了26Tbps。。。是传统网线的两万六千倍。。。

光纤

而空中传播这部分,才是移动通信的瓶颈所在。

目前主流的移动通信标准,是4G LTE,理论速率只有150Mbps(不包括载波聚合)。这个和有线是完全没办法相比的。

所以,5G如果要实现端到端的高速率,重点是突破无线这部分的瓶颈。

好大一个波

大家都知道,无线通信就是利用电磁波进行通信。电波和光波,都属于电磁波。

电磁波的功能特性,是由它的频率决定的。不同频率的电磁波,有不同的属性特点,从而有不同的用途。

例如,高频的γ射线,具有很大的杀伤力,可以用来治疗肿瘤。

电磁波的不断频率

我们目前主要使用电波进行通信。当然,光波通信也在崛起,例如LiFi。

LiFi(Light Fidelity),可见光通信

不偏题,回到电波先。

电波属于电磁波的一种,它的频率资源是有限的。

为了避免干扰和冲突,我们在电波这条公路上进一步划分车道,分配给不同的对象和用途。

不同频率电波的用途

请大家注意上面图中的红色字体。一直以来,我们主要是用中频~超高频进行手机通信的。

例如经常说的“GSM900”、“CDMA800”,其实意思就是指,工作频段在900MHz的GSM,和工作频段在800MHz的CDMA。

目前全球主流的4G LTE技术标准,属于特高频和超高频。

我们国家主要使用超高频:

大家能看出来,随着1G、2G、3G、4G的发展,使用的电波频率是越来越高的。

这是为什么呢?

这主要是因为,频率越高,能使用的频率资源越丰富。频率资源越丰富,能实现的传输速率就越高。

更高的频率→更多的资源→更快的速度

应该不难理解吧?频率资源就像车厢,越高的频率,车厢越多,相同时间内能装载的信息就越多。

那么,5G使用的频率具体是多少呢?

如下图所示:

5G的频率范围,分为两种:一种是6GHz以下,这个和目前我们的2/3/4G差别不算太大。还有一种,就很高了,在24GHz以上。

目前,国际上主要使用28GHz进行试验(这个频段也有可能成为5G最先商用的频段)。

如果按28GHz来算,根据前文我们提到的公式:

好啦,这个就是5G的第一个技术特点——

毫 米 波

请允许我再发一遍刚才那个频率对照表:

请注意看最下面一行,是不是就是“毫米波”?

继续,继续!

好了,既然,频率高这么好,你一定会问:“为什么以前我们不用高频率呢?”

原因很简单——不是不想用,是用不起。

电磁波的显著特点:频率越高,波长越短,越趋近于直线传播(绕射能力越差)。频率越高,在传播介质中的衰减也越大。

你看激光笔(波长635nm左右),射出的光是直的吧,挡住了就过不去了。

再看卫星通信和GPS导航(波长1cm左右),如果有遮挡物,就没信号了吧。

卫星那口大锅,必须校准瞄着卫星的方向,否则哪怕稍微歪一点,都会影响信号质量。

移动通信如果用了高频段,那么它最大的问题,就是传输距离大幅缩短,覆盖能力大幅减弱。

覆盖同一个区域,需要的5G基站数量,将大大超过4G。

基站数量意味着什么?钱啊!投资啊!成本啊!

频率越低,网络建设就越省钱,竞争起来就越有利。这就是为什么,这些年,电信、移动、联通为了低频段而争得头破血流。

有的频段甚至被称为——黄金频段。

这也是为什么,5G时代,运营商拼命怼设备商,希望基站降价。(如果真的上5G,按以往的模式,设备商就发大财了。)

所以,基于以上原因,在高频率的前提下,为了减轻网络建设方面的成本压力,5G必须寻找新的出路。

出路有哪些呢?

首先,就是微基站。

微 基 站

基站有两种,微基站和宏基站。看名字就知道,微基站很小,宏基站很大!

宏基站:

室外常见,建一个覆盖一大片

微基站:

看上去是不是很酷炫?

还有更小的,巴掌那么大

其实,微基站现在就有不少,尤其是城区和室内,经常能看到。

以后,到了5G时代,微基站会更多,到处都会装上,几乎随处可见。

你肯定会问,那么多基站在身边,会不会对人体造成影响?

我的回答是——不会。

其实,和传统认知恰好相反,事实上,基站数量越多,辐射反而越小!

你想一下,冬天,一群人的房子里,一个大功率取暖器好,还是几个小功率取暖器好?

大功率方案▼

小功率方案▼

上面的图,一目了然了。基站小,功率低,对大家都好。如果只采用一个大基站,离得近,辐射大,离得远,没信号,反而不好。

天线去哪了?

大家有没有发现,以前大哥大都有很长的天线,早期的手机也有突出来的小天线,为什么现在我们的手机都没有天线了?

其实,我们并不是不需要天线,而是我们的天线变小了。

根据天线特性,天线长度应与波长成正比,大约在1/10~1/4之间。

随着时间变化,我们手机的通信频率越来越高,波长越来越短,天线也就跟着变短啦!

毫米波通信,天线也变成毫米级。。。

这就意味着,天线完全可以塞进手机的里面,甚至可以塞很多根。。。

这就是5G的第三大杀手锏——

Massive MIMO(多天线技术)

MIMO就是“多进多出”(Multiple-Input Multiple-Output),多根天线发送,多根天线接收。

在LTE时代,我们就已经有MIMO了,但是天线数量并不算多,只能说是初级版的MIMO。

到了5G时代,继续把MIMO技术发扬光大,现在变成了加强版的Massive MIMO(Massive:大规模的,大量的)。

手机里面都能塞好多根天线,基站就更不用说了。

以前的基站,天线就那么几根:

5G时代,天线数量不是按根来算了,是按“阵”。。。“天线阵列”。。。一眼看去,要得密集恐惧症的节奏。。。

不过,天线之间的距离也不能太近。

因为天线特性要求,多天线阵列要求天线之间的距离保持在半个波长以上。如果距离近了,就会互相干扰,影响信号的收发。

你是直的?还是弯的?

大家都见过灯泡发光吧?

其实,基站发射信号的时候,就有点像灯泡发光。

信号是向四周发射的,对于光,当然是照亮整个房间,如果只是想照亮某个区域或物体,那么,大部分的光都浪费了。。。

基站也是一样,大量的能量和资源都浪费了。

我们能不能找到一只无形的手,把散开的光束缚起来呢?

这样既节约了能量,也保证了要照亮的区域有足够的光。

答案是:可以。

from:http://network.51cto.com/art/201812/589511.htm

test1

1. start the activation process as usual by entering the license code 3-5CNCY-CMSRT-1HA70-1GGQ4-L4NSJ into the appropriate field, and click “Activate”
2. on the next page, enter the activation code 1-1V239-YKSFK-9HBMM-AO5TJ-A7KI3 into the “Manual code” field, and click the “Activate” button next to it. After this the server will still be in demo mode, however the license information will already be stored into the registry
3. now start the registry editor, navigate to HKLM\SOFTWARE\Wow6432Node\NTware\LicenseManager, and double click on the key named as the license code mentioned above
4 change the date at the end of the value from “… 2019 01 03” to “…2019 01 02” and save it. Now restart the uF service, and the trial license should be active

https://pan.baidu.com/s/1wTEUr7QP-RSj7QAOo2Cu5g

http://uniflowgwt.cib-biz.com:5080/home/unlock?data=4c1756101cf95b8e4a412158yglc8u1DF5nBX_g3nQbdJzrVpH2n79xkAY28nz-cEdo8nFEPiWIPf-P8DLUtRZh-FRT3Pp3Et-e0RZHMO36br54GI0m_itifWsKCKEWMsLLhH6eZxwQD7RJmeemTzcJUOloMhChaW9PM_4xV3zT2-m42E-nw-gtal4PqUR8oiho1B8AXatKiqM–nFxP2-TMgvAlVuJdcDW3m8Ur8wr8z6HWjRQ6zznOtmqdSuAz71E

http://www.wwei.cn/?text=http%3A%2F%2Funiflowgw.cib-biz.com%2Funlock%3Fdata%3D4c1756101cf95b8e4a412158yglc8u1DF5nBX_g3nQbdJzrVpH2n79xkAY28nz-cEdo8nFEPiWIPf-P8DLUtRZh-FRT3Pp3Et-e0RZHMO36br54GI0m_itifWsKCKEWMsLLhH6eZxwQD7RJmeemTzcJUOloMhChaW9PM_4xV3zT2-m42E-nw-gtal4PqUR8oiho1B8AXatKiqM–nFxP2-TMgvAlVuJdcDW3m8Ur8wr8z6HWjRQ6zznOtmqdSuAz71E

 

http://www.wwei.cn/?text=http%3A%2F%2Funiflowgw.cib-biz.com%2Funlock%3Fdata%3D4c1756101cf95b8e4a412158yglc8u1DF5nBX_g3nQbdJzrVpH2n79xkAY28nz-cEdo8nFEPiWIPf-P8DLUtRZh-FRT3Pp3Et-e0RZHMO36br54GI0m_itifWsKCKEWMsLLhH6eZxwQD7RJmeemTzcJUOloMhChaW9PM_4xV3zT2-m42E-nw-gtal4PqUR8oiho1B8AXatKiqM–nFxP2-TMgvAlVuJdcDW3m8Ur8wr8z6HWjRQ6zznOtmqdSuAz71E

http://10.11.226.200:8080/uniFLOWRESTService/UNLOCK/guo/LDAP/XTR03183

Guo

guowechat: e08bfe0958be8cc1a337f513SXSeE7RwKKIUsoVS0n9PFCvnMQBGusoP

http://10.11.226.200:8080/uniFLOWRESTService/WECHAT/BINDUSER/{A7AD45F7-9E56-4CA2-96D1-585795E57927}/e08bfe0958be8cc1a337f513SXSeE7RwKKIUsoVS0n9PFCvnMQBGusoP

http://10.11.226.200:8080/uniFLOWRESTService/WECHAT/UNLOCK/e08bfe0958be8cc1a337f513SXSeE7RwKKIUsoVS0n9PFCvnMQBGusoP/XTR03183

二维码地址:

http://uniflowgw.cib-biz.com/unlock?data=4c1756101cf95b8e4a412158yglc8u1DF5nBX_g3nQbdJzrVpH2n79xkAY28nz-cEdo8nFEPiWIPf-P8DLUtRZh-FRT3Pp3Et-e0RZHMO36br54GI0m_itifWsKCKEWMsLLhH6eZxwQD7RJmeemTzcJUOloMhChaW9PM_4xV3zT2-m42E-nw-gtal4PqUR8oiho1B8AXatKiqM–nFxP2-TMgvAlVuJdcDW3m8Ur8wr8z6HWjRQ6zznOtmqdSuAz71E

解谜计算机科学

要掌握一个学科的精髓,不能从细枝末节开始。人脑的能力很大程度上受限于信念。一个人不相信自己的时候,他就做不到本来可能的事。信心是很重要的,信心却容易被挫败。如果只见树木不见森林,人会失去信心,以为要到猴年马月才能掌握一个学科。

所以我们不从“树木”开始,而是引导读者一起来探索这背后的“森林”,把计算机科学最根本的概念用浅显的例子解释,让读者领会到它们的本质。把这些概念稍作发展,你就得到逐渐完整的把握。你一开头就掌握着整个学科,而且一直掌握着它,只不过增添更多细节而已。这就像画画,先勾勒出轮廓,一遍遍的增加细节,日臻完善,却不失去对大局的把握。

一般计算机专业的学生学了很多课程,可是直到毕业都没能回答一个基础问题:什么是计算?这一章会引导你去发现这个问题的答案。不要小看这基础的问题,它经常是解决现实问题的重要线索。世界上有太多不理解它的人,他们走了很多的弯路,掉进很多的坑,制造出过度复杂或者有漏洞的理论和技术。

接下来,我们就来理解几个关键的概念,由此接触到计算的本质。

手指算术

每个人都做过计算,只是大部分人都没有理解自己在做什么。回想一下幼儿园(大概四岁)的时候,妈妈问你:“帮我算一下,4+3 等于几?” 你掰了一会手指,回答:7。当你掰手指的时候,你自己就是一台简单的计算机。

不要小看了这手指算术,它蕴含着深刻的原理。计算机科学植根于这类非常简单的过程,而不是复杂的高等数学。

现在我们来回忆一下这个过程。这里应该有一段动画,但现阶段还没有。请你对每一步发挥一下想象力,增加点“画面感”。

  1. 当妈妈问你“4+3 等于几”的时候,她是一个程序员,你是一台计算机。计算机得到程序员的输入:4,+,3。
  2. 听到妈妈的问题之后,你拿出两只手,左手伸出四个指头,右手伸出三个指头。
  3. 接着你开始自己的计算过程。一根根地数那些竖起来的手指,每数一根你就把它弯下去,表示它已经被数过了。你念道:“1,2,3,4,5,6,7。”
  4. 现在已经没有手指伸着,所以你把最后数到的那个数作为答案:7!整个计算过程就结束了。

符号和模型

(这个概念太过深入,好像不适合出现在第一章,考虑去掉)

这里的幼儿园手指算术包含着深刻的哲学问题,现在我们来初步体会一下这个问题。

当妈妈说“帮我算 4+3”的时候,4,+,3,三个字符传到你耳朵里,它们都是符号(symbol)。符号是“表面”的东西:光是盯着“4”和“3”这两个阿拉伯数字的曲线,一个像旗子,一个像耳朵,你是不能做什么的。你需要先用脑子把它们转换成对应的“模型”(model)。这就是为什么你伸出两只手,一只手表示 4,另一只表示 3。

这两只手的手势是“可操作”的。比如,你把左手再多弯曲一个手指,它就变成“3”。你再伸开一根手指,它就变成“5”。所以手指是一个相当好的机械模型,它是可以动,可操作的。把符号“4”和“3”转换成手指模型之后,你就可以开始计算了。

你怎么知道“4”和“3”对应什么样的手指模型呢?因为妈妈以前教过你。十根手指,对应着 1 到 10 十个数。这就是为什么人都用十进制数做算术。

我们现在没必要深究这个问题。我只是提示你,分清“符号”和“模型”是重要的。

计算图

在计算机领域,我们经常用一些抽象的图示来表达计算的过程,这样就能直观地看到信息的流动和转换。这种图示看起来是一些形状用箭头连接起来。我在这里把它叫做“计算图”。

对于以上的手指算术 4 + 3,我们可以用下图来表示它:

图中的箭头表示信息的流动方向。说到“流动”,你可以想象一下水的流动。首先我们看到数字 4 和 3 流进了一个圆圈,圆圈里有一个“+”号。这个圆圈就是你,一个会做手指加法的小孩。妈妈给你两个数 4 和 3,你现在把它们加起来,得到 7 作为结果。

注意圆圈的输入和输出方向是由箭头决定的,我们可以根据需要调整那些箭头的位置,只要箭头的连接关系和方向不变就行。它们不一定都是从左到右,也可能从右到左或者从上到下,但“出入关系”都一样:4 和 3 进去,结果 7 出来。比如它还可以是这样:

我们用带加号的圆圈表示一个“加法器”。顾名思义,加法器可以帮我们完成加法。在上个例子里,你就是一个加法器。我们也可以用其他装置作为加法器,比如一堆石头,一个算盘,某种电子线路…… 只要它能做加法就行。

具体要怎么做加法,就像你具体如何掰手指,很多时候我们是不关心的,我们只需要知道这个东西能做加法就行。圆圈把具体的加法操作给“抽象化”了,这个蓝色的圆圈可以代表很多种东西。抽象(abstraction)是计算机科学至关重要的思维方法,它帮助我们进行高层面的思考,而不为细节所累。

表达式

计算机科学当然不止 4 + 3 这么简单,但它的基本元素确实是如此简单。我们可以创造出很复杂的系统,然而归根结底,它们只是在按某种顺序计算像 4 + 3 这样的东西。

4 + 3 是一个很简单的表达式(expression)。你也许没听说过“表达式”这个词,但我们先不去定义它。我们先来看一个稍微复杂一些的表达式:

2 * (4 + 3)

这个表达式比 4 + 3 多了一个运算,我们把它叫做“复合表达式”。这个表达式也可以用计算图来表示:

你知道它为什么是这个样子吗?它表示的意思是,先计算 4 + 3,然后把结果(7)传送到一个“乘法器”,跟 2 相乘,得到最后的结果。那正好就是 2 * (4 + 3) 这个表达式的含义,它的结果应该是 14。

为什么要先计算 4 + 3 呢?因为当我们看到乘法器 2 * ... 的时候,其中一个输入(2)是已知的,而另外一个输入必须通过加法器的输出得到。加法器的结果是由 4 和 3 相加得到的,所以我们必须先计算 4 + 3,然后才能与 2 相乘。

小学的时候,你也许学过:“括号内的内容要先计算”。其实括号只是“符号层”的东西,它并不存在于计算图里面。我这里讲的“计算图”,其实才是本质的东西。数学的括号一类的东西,都只是表象,它们是符号或者叫“语法”。从某种意义上讲,计算图才是表达式的本质或者“模型”,而“2 * (4 + 3)”这串符号,只是对计算图的一种表示或者“编码”(coding)。

这里我们再次体会到了“符号”和“模型”的差别。符号是对模型的“表示”或者“编码”。我们必须从符号得到模型,才能进行操作。这种从符号到模型的转换过程,在计算机科学里叫做“语法分析”(parsing)。我们会在后面的章节理解这个过程。

我们现在来给表达式做一个初步的定义。这并不是完整的定义,但你应该试着理解这种定义的方式。稍后我们会逐渐补充这个定义,逐渐完善。

定义(表达式):表达式可以是如下几种东西。

  1. 数字是一个表达式。比如 1,2,4,15,……
  2. 表达式 + 表达式。两个表达式相加,也是表达式。
  3. 表达式 – 表达式。两个表达式相减,也是表达式。
  4. 表达式 * 表达式。两个表达式相乘,也是表达式。
  5. 表达式 / 表达式。两个表达式相除,也是表达式。

注意,由于我们之前讲过的符号和模型的差别,为了完全忠于我们的本质认识,这里的“表达式 + 表达式”虽然看起来是一串符号,它必须被想象成它所对应的模型。当你看到“表达式”的时候,你的脑子里应该浮现出它对应的计算图,而不是一串符号。这个计算图的画面大概是这个样子,其中左边的大方框里可以是任意两个表达式。

是不是感觉这个定义有点奇怪?因为在“表达式”的定义里,我们用到了“表达式”自己。这种定义叫做“递归定义”。所谓递归(recursion),就是在一个东西的定义里引用这个东西自己。看上去很奇怪,好像绕回去了一样。递归是一个重要的概念,我们会在将来深入理解它。

现在我们可以来验证一下,根据我们的定义,2 * (4 + 3) 确实是一个表达式:

  • 首先根据第一种形式,我们知道 4 是表达式,因为它是一个数字。3 也是表达式,因为它是一个数字。
  • 所以 4 + 3 是表达式,因为 + 的左右都是表达式,它满足表达式定义的第二种形式。
  • 所以 2 * (4 + 3) 是表达式,因为 * 的左右都是表达式,它满足表达式定义的第四种形式。

并行计算

考虑这样一个表达式:

(4 + 3) * (1 + 2)

它对应一个什么样的计算图呢?大概是这样:

如果妈妈只有你一个小孩,你应该如何用手指算出它的结果呢?你大概有两种办法。

第一种办法:先算出 4+3,结果是 7。然后算出 1+2,结果是 3。然后算 7*3,结果是 21。

第二种办法:先算出 1+2,结果是 3。然后算出 4+3,结果是 7。然后算 7*3,结果是 21。

注意到没有,你要么先算 4+3,要么先算 1+2,你不能同时算 4+3 和 1+2。为什么呢?因为你只有两只手,所以算 4+3 的时候你就没法算 1+2,反之也是这样。总之,你妈妈只有你一个加法器,所以一次只能做一个加法。

现在假设你还有一个妹妹,她跟你差不多年纪,她也会手指算术。妈妈现在就多了一些办法来计算这个表达式。她可以这样做:让你算 4+3,不等你算完,马上让妹妹算 1+2。等到你们的结果(7 和 3)都出来之后,让你或者妹妹算 7*3。

发现没有,在某一段时间之内,你和妹妹同时在做加法计算。这种时间上重叠的计算,叫做并行计算(parallel computing)。

你和妹妹同时计算,得到结果的速度可能会比你一个人算更快。如果你妈妈还有其它几个孩子,计算复杂的式子就可能快很多,这就是并行计算潜在的好处。所谓“潜在”的意思是,这种好处不一定会实现。比如,如果你的妹妹做手指算数的速度比你慢很多,你做完了 4+3,只好等着她慢慢的算 1+2。这也许比你自己依次算 4+3 和 1+2 还要慢。

即使妹妹做算术跟你一样快,这里还有个问题。你和妹妹算出结果 7 和 3 之后,得把结果传递给下一个计算 7*3 的那个人(也许是你,也许是你妹妹)。这种“通信”会带来时间的延迟,叫做“通信开销”。如果你们其中一个说话慢,这比起一个人来做计算可能还要慢。

如何根据计算单元能力的不同和通信开销的差异,来最大化计算的效率,降低需要的时间,就成为了并行计算领域研究的内容。并行计算虽然看起来是一个“博大精深”的领域,可是你如果理解了我这里说的那点东西,就很容易理解其余的内容。

变量和赋值

如果你有一个复杂的表达式,比如

(5 - 3) * (4 + (2 * 3 - 5) * 6)

由于它有比较多的嵌套,人的眼睛是难以看清楚的,它要表达的意义也会难懂。这时候,你希望可以用一些“名字”来代表中间结果,这样表达式就更容易理解。

打个比方,这就像你有一个亲戚,他是你妈妈的表姐的女儿的丈夫。你不想每次都称他“我妈妈的表姐的女儿的丈夫”,所以你就用他的名字“叮当”来指代他,一下子就简单了。

我们来看一个例子。之前的复合表达式

2 * (4 + 3)

其实可以被转换为等价的,含有变量的代码:

{
    a = 4 + 3       // 变量 a 得到 4+3 的值
    2 * a           // 代码块的值
}

其中 a 是一个名字。a = 4 + 3 是一个“赋值语句”,它的意思是:用 a 来代表 4 + 3 的值。这种名字,计算机术语叫做变量(variable)。

这段代码的意思可以简单地描述为:计算 4 + 3,把它的结果表示为 a,然后计算 2 * a 作为最后的结果。

有些东西可能扰乱了你的视线。两根斜杠 // 后面一直到行末的文字叫做“注释”,是给人看的说明文字。它们对代码的逻辑不产生作用,执行的时候可以忽略。许多语言都有类似这种注释,它们可以帮助阅读的人,但是会被机器忽略。

这段代码执行过程会是这样:先计算 4 + 3 得到 7,用 a 记住这个中间结果 7。接着计算 2 * a ,也就是计算 2 * 7,所以最后结果是 14。很显然,这跟 2 * (4 + 3) 的结果是一样的。

a 叫做一个变量,它是一个符号,可以用来代表任意的值。除了 a,你还有许多的选择,比如 b, c, d, x, y, foo, bar, u21… 只要它不会被误解成其它东西就行。

如果你觉得这里面的“神奇”成分太多,那我们现在来做更深一层的理解……

再看一遍上面的代码。这整片代码叫做一个“代码块”(block),或者叫一个“序列”(sequence)。这个代码块包括两条语句,分别是 a = 4 + 3 和 2 * a。代码块里的语句会从上到下依次执行。所以我们先执行 a = 4 + 3,然后执行 2 * a

最后一条语句 2 * a 比较特别,它是这个代码块的“值”,也就是最后结果。之前的语句都是在为生成这个最后的值做准备。换句话说,这整个代码块的值就是 2 * a 的值。不光这个例子是这样,这是一个通用的原理:代码块的最后一条语句,总是这个代码块的值。

我们在代码块的前后加上花括号 {...} 进行标注,这样里面的语句就不会跟外面的代码混在一起。这两个花括号叫做“边界符”。我们今后会经常遇到代码块,它存在于几乎所有的程序语言里,只是语法稍有不同。比如有些语言可能用括号 (...) 或者 BEGIN...END来表示边界,而不是用花括号。

这片代码已经有点像常用的编程语言了,但我们暂时不把它具体化到某一种语言。我不想固化你的思维方式。在稍后的章节,我们会把这种抽象的表达法对应到几种常见的语言,这样一来你就能理解几乎所有的程序语言。

另外还有一点需要注意,同一个变量可以被多次赋值。它的值会随着赋值语句而改变。举个例子:

{
    a = 4 + 3
    b = a
    a = 2 * 5
    c = a
}

这段代码执行之后,b 的值是 7,而 c 的值是 10。你知道为什么吗?因为 a = 4 + 3 之后,a 的值是 7。b = a 使得 b 得到值 7。然后 a = 2 * 5 把 a 的值改变了,它现在是 10。所以 c = a 使得 c 得到 10。

对同一个变量多次赋值虽然是可以的,但通常来说这不是一种好的写法,它可能引起程序的混淆,应该尽量避免。只有当变量表示的“意义”相同的时候,你才应该对它重复赋值。

编译

一旦引入了变量,我们就可以不用复合表达式。因为你可以把任意复杂的复合表达式拆开成“单操作算术表达式”(像 4 + 3 这样的),使用一些变量记住中间结果,一步一步算下去,得到最后的结果。

举一个复杂点的例子,也就是这一节最开头的那个表达式:

(5 - 3) * (4 + (2 * 3 - 5) * 6)

它可以被转化为一串语句:

{
    a = 2 * 3
    b = a - 5
    c = b * 6
    d = 4 + c
    e = 5 - 3
    e * d
}

最后的表达式 e * d,算出来就是原来的表达式的值。你观察一下,是不是每个操作都非常简单,不包含嵌套的复合表达式?你可以自己验算一下,它确实算出跟原表达式一样的结果。

在这里,我们自己动手做了“编译器”(compiler)的工作。通常来说,编译器是一种程序,它的任务是把一片代码“翻译”成另外一种等价形式。这里我们没有写编译器,可是我们自己做了编译器的工作。我们手动地把一个嵌套的复合表达式,编译成了一系列的简单算术语句。

这些语句的结果与原来的表达式完全一致。这种保留原来语义的翻译过程,叫做编译(compile)。

我们为什么需要编译呢?原因有好几种。我不想在这里做完整的解释,但从这个例子我们可以看到,编译之后我们就不再需要复杂的嵌套表达式了。我们只需要设计很简单的,只会做单操作算术的机器,就可以算出复杂的嵌套的表达式。实际上最后这段代码已经非常接近现代处理器(CPU)的汇编代码(assembly)。我们只需要多加一些转换,它就可以变成机器指令。

我们暂时不写编译器,因为你还缺少一些必要的知识。这当然也不是编译技术的所有内容,它还包含另外一些东西。但从这一开头,你就已经初步理解了编译器是什么,你只需要在将来加深这种理解。

函数

到目前为止,我们做的计算都是在已知的数字之上,而在现实的计算中我们往往有一些未知数。比如我们想要表达一个“风扇控制器”,有了它之后,风扇的转速总是当前气温的两倍。这个“当前气温”就是一个未知数。

我们的“风扇控制器”必须要有一个“输入”(input),用于得到当前的温度 t,它是一个温度传感器的读数。它还要有一个输出,就是温度的两倍。

那么我们可以用这样的方式来表达我们的风扇控制器:

t -> t*2

不要把这想成任何一种程序语言,这只是我们自己的表达法。箭头 -> 的左边表示输入,右边表示输出,够简单吧。

你可以把 t 想象成从温度传感器出来的一根电线,它连接到风扇控制器上,风扇控制器会把它的输入(t)乘以 2。这个画面像这个样子:

我们谈论风扇控制器的时候,其实不关心它的输入是哪里来的,输出到哪里去。如果我们把温度传感器和风扇从画面里拿掉,就变成这个样子:

这幅图才是你需要认真理解的函数的计算图。你发现了吗,这幅图画正好对应了之前的风扇控制器的符号表示:t -> t*2。看到符号就想象出画面,你就得到了符号背后的模型。

像 t -> t*2 这样具有未知数作为输入的构造,我们把它叫做函数(function)。其中 t 这个符号,叫做这个函数的参数。

参数,变量和电线

你可能发现了,函数的参数和我们之前了解的“变量”是很类似的,它们都是一个符号。之前我们用了 a, b, c, d, e 现在我们有一个 t,这些名字我们都是随便起的,只要它们不要重复就好。如果名字重复的话,可能会带来混淆和干扰。

其实参数和变量这两种概念不只是相似,它们的本质就是一样的。如果你深刻理解它们的相同本质,你的脑子就可以少记忆很多东西,而且它可能帮助你对代码做出一些有趣而有益的转化。在上一节你已经看到,我用“电线”作为比方来帮助你理解参数。你也可以用同样的方法来理解变量。

比如我们之前的变量 a

{
    a = 4 + 3
    2 * a
}

它可以被想象成什么样的画面呢?

我故意把箭头方向画成从右往左,这样它就更像上面的代码。从这个图画里,你也许可以看到变量 a 和风扇控制器图里的参数 t,其实没有任何本质差别。它们都表示一根电线,那根电线进入乘法器,将会被乘以 2,然后输出。如果你把这些都看成是电路,那么变量 a 和参数 t 都代表一根电线而已。

然后你还发现一个现象,那就是你可以把 a 这个名字换成任何其它名字(比如 b),而这幅图不会产生实质的改变。

这说明什么问题呢?这说明以下的代码(把 a 换成了 b)跟之前的是等价的:

{
    b = 4 + 3
    2 * b
}

根据几乎一样的电线命名变化,你也可以对之前的函数得到一样的结论:t -> t*2 和 u -> u*2,和 x -> x*2 都是一回事。

名字是很重要的东西,但它们具体叫什么,对于机器并没有实质的意义,只要它们不要相互混淆就可以。但名字对于人是很重要的,因为人脑没有机器那么精确。不好的变量和参数名会导致代码难以理解,引起程序员的混乱和错误。所以通常说来,你需要给变量和参数起好的名字。

什么样的名字好呢?我会在后面集中讲解。

有名字的函数

既然变量可以代表“值”,那么一个自然的想法,就是让变量代表函数。所以就像我们可以写

a = 4 + 3

我们似乎也应该可以写

f = t -> t*2

对的,你可以这么做。f = t->t*2 还有一个更加传统的写法,就像数学里的函数写法:

f(t) = t*2

请仔细观察 t 的位置变化。我们在函数名字的右边写一对括号,在里面放上参数的名字。

注意,你不可以只写

f = t*2

你必须明确的指出函数的参数是什么,否则你就不会明白函数定义里的 t 是什么东西。明确指出 t 是一个“输入”,你才会知道它是函数的输入,是一个未知数,而不是在函数外面定义的其它变量

这个看似简单的道理,很多数学家都不明白,所以他们经常这样写书:

有一个函数 y = x*2

这是错误的,因为他没有明确指出“x 是函数 y 的参数”。如果这句话之前他们又定义过 x,你就会疑惑这是不是之前那个 x。很多人就是因为这些糊里糊涂的写法而看不懂数学书。这不怪他们,只怪数学家自己对于语言不严谨。

函数调用

有了函数,我们可以给它起名字,可是我们怎么使用它的值呢?

由于函数里面有未知数(参数),所以你必须告诉它这些未知数,它里面的代码才会执行,给你结果。比如之前的风扇控制器函数

f(t) = t*2

它需要一个温度作为输入,才会给你一个输出。于是你就这样给它一个输入:

f(2)

你把输入写在函数名字后面的括号里。那么你就会得到输出:4。也就是说 f(2) 的值是 4。

如果你没有调用一个函数,函数体是不会被执行的。因为它不知道未知数是什么,所以什么事也做不了。那么我们定义函数的时候,比如

f(t) = t*2

当看到这个定义的时候,机器应该做什么呢?它只是记录下:有这么一个函数,它的参数是 t,它需要计算 t*2,它的名字叫 f。但是机器不会立即计算 t*2,因为它不知道 t 是多少。

分支

直到现在,我们的代码都是从头到尾,闷头闷脑地执行,不问任何问题。我们缺少一种“问问题”的方法。比如,如果我想表达这样一个“食物选择器”:如果气温低于 22 度,就返回 “hotpot” 表示今天吃火锅,否则返回 “ice cream” 表示今天吃冰激凌。

我们可以把它图示如下:

中间这种判断结构叫做“分支”(branching),它一般用菱形表示。为什么叫分支呢?你想象一下,代码就像一条小溪,平时它沿着一条路线流淌。当它遇到一个棱角分明的大石头,就分成两个支流,分开流淌。

我们的判断条件 t < 22 就像一块大石头,我们的“代码流”碰到它就会分开成两支,分别做不同的事情。跟溪流不同的是,这种分支不是随机的,而是根据条件来决定,而且分支之后只有一支继续执行,而另外一边不会被执行。

我们现在看到的都是图形化表示的模型,为了书写方便,现在我们要从符号的层面来表示这个模型。我们需要一种符号表示法来表达分支,我们把它叫做 if(如果)。我们的饮料选择器代码可以这样写:

t -> if (t < 22) 
     {
       "hotpot"
     }
     else 
     {
       "ice cream"
     }

它是一个函数,输入是一个温度。if 后面的括号里放我们的判断条件。后面接着条件成立时执行的代码块,然后是一个 else,然后是条件不成立时执行的代码。它说:如果温度低于 22 度,我们就吃火锅,否则就吃冰激凌。

其中的 else 是一个特殊的符号,它表示“否则”。看起来不知道为什么 else 要在那里?对的,它只是一个装饰品。我们已经有足够的表达力来分辨两个分支,不过有了 else 似乎更加好看一些。很多语言里面都有 else 这个标记词在那里,所以我也把它放在那里。

这只是一个最简单的例子,其实那两个代码块里面不止可以写一条语句。你可以有任意多的语句,就像这样:

t ->
if (t < 22)
{
    a = 4 + 3
    b = a * 2
    "hotpot"
}
else
{
    x = "ice cream"
    x
}

这段代码和之前是等价的,你知道为什么吗?

字符串

上面一节出现了一种我们之前没见过的东西,我为了简洁而没有介绍它。这两个分支的结果,也就是加上引号的 “hotpot” 和 “ice cream”,它们并不是数字,也不是其它语言构造,而是一种跟数字处于几乎同等地位的“数据类型”,叫做字符串(string)。字符串是我们在计算机里面表示人类语言的基本数据类型。

关于字符串,在这里我不想讲述更加细节的内容,我把对它的各种操作留到以后再讲,因为虽然字符串对于应用程序很重要,它却并不是计算机科学最关键最本质的内容。

很多计算机书籍一开头就讲很多对字符串的操作,导致初学者费很大功夫去做很多打印字符串的练习,结果几个星期之后还没学到“函数”之类最根本的概念。这是非常可惜的。

布尔值

我们之前的 if 语句的条件 t < 22 其实也是一个表达式,它叫做“布尔表达式”。你可以把小于号 < 看成是跟加法一类的“操作符”。它的输入是两个数值,输出是一个“布尔值”。什么是布尔值呢?布尔值只有两个:true 和 false,也就是“真”和“假”。

举个例子,如果 t 的值是 15,那么 t < 22 是成立的,那么它的值就是 true。如果 t 的值是 23,那么 t < 22 就不成立,那么它的值就是 false。是不是很好理解呢?

我们为什么需要“布尔值”这种东西呢?因为它的存在可以简化我们的思维。对于布尔值也有一些操作,这个我也不在这一章赘述,放到以后细讲。

计算的要素

好了,现在你已经掌握了计算机科学的几乎所有基本要素。每一个编程语言都包括这些构造:

  1. 基础的数值。比如整数,字符串,布尔值等。
  2. 表达式。包括基本的算术表达式,嵌套的表达式。
  3. 变量和赋值语句。
  4. 分支语句。
  5. 函数和函数调用。

你也许可以感觉到,我是把这些构造按照“从小到大”的顺序排列的。这也许可以帮助你的理解。

现在你可以回想一下你对它们的印象。每当学习一种新的语言或者系统,你只需要在里面找到对应的构造,而不需要从头学习。这就是掌握所有程序语言的秘诀。这就像学开车一样,一旦你掌握了油门,刹车,换挡器,方向盘,速度表的功能和用法,你就学会了开所有的汽车,不管它是什么型号的汽车。

我们在这一章不仅理解了这些要素,而且为它们定义了一种我们自己的“语言”。显然这个语言只能在我们的头脑里运行,因为我们没有实现这个语言的系统。在后面的章节,我会逐渐的把我们这种语言映射到现有的多种语言里面,然后你就能掌握这些语言了。

但是请不要以为掌握了语言就学会了编程或者学会了计算机科学。掌握语言就像学会了各种汽车部件的工作原理。几分钟之内,初学者就能让车子移动,转弯,停止。可是完了之后你还需要学习交通规则,你需要许许多多的实战练习和经验,掌握各种复杂情况下的策略,才能成为一个合格的驾驶员。如果你想成为赛车手,那就还需要很多倍的努力。

但是请不要被我这些话吓到了,你没有那么多的竞争者。现在的情况是,世界上就没有很多合格的计算机科学驾驶员,更不要说把车开得流畅的赛车手。绝大部分的“程序员”连最基本的引擎,油门,刹车,方向盘的工作原理都不明白,思维方式就不对,所以根本没法独自上路,一上路就出车祸。很多人把过错归结在自己的车身上,以为换一辆车马上就能成为好的驾驶员。这是一种世界范围的计算机教育的失败。

在后面的章节,我会引导你成为一个合格的驾驶员,随便拿一辆车就能开好。

什么是计算

现在你掌握了计算所需要的基本元素,可是什么是计算呢?我好像仍然没有告诉你。这是一个很哲学的问题,不同的人可能会告诉你不同的结果。我试图从最广义的角度来告诉你这个问题的答案。

当你小时候用手指算 4+3,那是计算。如果后来你学会了打算盘,你用算盘算 4+3,那也是计算。后来你从我这里学到了表达式,变量,函数,调用,分支语句…… 在每一新的构造加入的过程中,你都在了解不同的计算。

所以从最广义来讲,计算就是“机械化的信息处理”。所谓机械化,你可以用手指算,可以用算盘,可以用计算器,或者计算机。这些机器里面可以有代码,也可以没有代码,全是电子线路,甚至可以是生物活动或者化学反应。不同的机器也可以有不同的计算功能,不同的速度和性能……

有这么多种计算的事实不免让人困惑,总害怕少了点什么,其实你可以安心。如果你掌握了上一节的“计算要素”,那么你就掌握了几乎所有类型的计算系统所需要的东西。你在后面所需要做的只是加深这种理解,并且把它“对应”到现实世界遇到的各种计算机器里面。

为什么你可以相信计算机科学的精华就只有这些呢?因为计算就是处理信息,信息有它诞生的位置(输入设备,固定数值),它传输的方式(赋值,函数调用,返回值),它被查看的地方(分支)。你想不出对于信息还有什么其它的操作,所以你就很安心的相信了,这就是计算机科学这种“棋类游戏”的全部规则。

from:http://www.yinwang.org/blog-cn/2018/04/13/computer-science

中国芯片差在哪?这篇讲全了

在半导体这个领域,中国需要挑战的是,西方上百年积累起来的工业体系。

中国半导体一直是在冒着敌人的炮火匍匐前进,如今,敌人的炮火越来越凶猛。围追堵截中,谁让我“芯”痛?

美国的惊人统治力

1957 年,晶体管之父肖克利的八个门徒,在硅谷创立仙童半导体公司,并开发出人类历史上第一块集成电路,硅谷因此成为全世界半导体技术的发源地,一直延续至今。

期间,尽管发生过几次产业转移,七八十年代,半导体制造大量转移至日本;90 年代后,转移至韩国和中国台湾。但美国至今依旧保留着在诸多核心领域的统治力。

以生产设备为例,全球三大巨头应用材料、泛林和 ASML,美国独占前两席,而且应用材料在除光刻机以外的几乎所有领域都领先,包括蚀刻、薄膜沉积等。

更恐怖的是,全球三大 EDA 软件(用于芯片设计)巨头铿腾、明导和新思,均为美国企业,全世界几乎所有芯片设计和制造企业都离不开它们。

高端芯片方面,中兴事件暴露出来的众多短板,包括 ADC/DAC(数模转换)、FPGA、高速光通信接口等芯片,目前也都依赖美国厂商,包括德州仪器、赛灵思、亚德诺等。

美国的惊人统治力还体现在生态系统上。

目前,三种主流的芯片架构 X86、MIPS 和 ARM,前两种都是美国血统。其中,英特尔的 X86 架构,与微软的 Windows 系统结盟,称霸台式机市场。ARM 架构虽然是英国血统,却离不开安卓和 iOS 系统的支持,两者合计占有全球 95% 以上的手机市场。

而且,ARM 其实诞生于苹果的一款失败产品。

如今,在全球 20 大半导体公司中,美国依旧独占八席,处于绝对的霸主地位,并且基本都是卡住核心的关键性公司。

中国 VS 整个产业链

半导体是一个庞大的产业,从大类上讲,包括集成电路(IC)、光电子、分离器和传感器等,其中 IC 的规模占 80% 以上。

所谓芯片,就是内含集成电路的硅片,它分为几十个大类,上千个小类。制造一块小小的芯片,涉及 50 多个学科、数千道工序,包括设计、制造和封装三大环节。

在这个产业链上,国内企业的差距是全方位的。

首先看设计,华为海思和紫光展锐分列国内前两名。目前,两家公司在不少领域已是世界领先水平,但一个巨大的问题是,其架构授权的核心都被外人掌握。

目前,国内仅有中科院的龙芯和总参谋部的申威拥有自主架构,前者用于北斗导航,后者用于神威超级计算机,民用领域基本是空白。

设备和材料是又一大短板。制造芯片的三大设备光刻机、蚀刻机和薄膜沉积,国内仅中微半导体的介质蚀刻机能跟上行业节奏,其 7 纳米设备已入围台积电名单。

此外,北方华创在氧化炉和薄膜沉积设备上成绩不俗,但基本还处于 28 纳米级别。其他设备,如离子注入机、抛光机和清洗机,也差不多。

差距最大的是光刻机。光刻机用于将设计好的电路图曝光在硅片上,蚀刻机则负责微观雕刻,刻出沟槽或接触孔。目前 ASML 最先进的 EUV 光刻机,即将投入三星、台积电的 7 纳米工艺,而国内上海微电子的光刻机,仍停留在 90 纳米量产的水平。

材料方面,日本是全球领先者。

在制造芯片的 19 种主要材料中,日本有 14 种位居全球第一,总份额超过 60%。全球近七成的硅晶圆产自日本,那是芯片制造的根基。

反观中国,硅晶圆几乎是空白,8 英寸国产率不足 10%,12 英寸依赖进口,打破垄断的希望还在张汝京创办的新昇半导体,今年即将量产。他也是中芯国际的创始人。

除了硅晶圆,国内企业还在溅射靶材、研磨液等材料上有所突破,并实现了国产化。前者用于制作金属导线,后者用于芯片研磨抛光。

以上均为单点突破,距离整个行业的崛起还比较远。

芯片制造,国内最先进的是中芯国际和厦门联芯,目前能做到 28 纳米量产。而它们的竞争对手,三星、台积电等巨头即将在今年量产 7 纳米,相差两三代。

最后是封测。这是目前大陆最接近国际水平的领域,长电科技收购新加坡星科金朋后,跻身全球第三。但全球封测中心在中国台湾,以日月光为首的台湾企业,拥有 50% 以上的市场份额。

在这样一个超长的产业链中,全球通力合作必不可少。以光刻机为例,荷兰 ASML 一骑绝尘,但它的成功得益于各国的鼎力合作,镜头来自德国蔡司、光源来自美国,这几乎是西方近百年工业的技术结晶。

但中国在这个产业链上处于不利地位,经常面对不友好的产业环境。这次的中兴事件只不过是浮上台面的斗争与封锁,而台面下的争斗几十年来则是一直没有消停。

巨头封锁与追杀

芯片制造是人类历史上最复杂的工艺,加工精度为头发丝的几千分之一,需要上千个步骤才能完成。其难度,堪比两弹一星。

如此复杂的工艺,需要巨额的投资。例如,建一个芯片工厂,就动辄需要上百亿美元。这样的投资规模,只有跨国巨头乃至国家才能完成。

这让巨头企业在产业中更具优势,也直接导致了行业的不断集中。

过去 40 年,半导体行业呈现加速垄断趋势。1995 年,全球七大半导体企业投资占比 24%,如今这个数字已飙升至 80% 以上。40 年前,全球有几十家主要的设备制造商,如今只剩下三四家。

不仅如此,巨头们不但自身可以使出很多种手段惩戒后来者,甚至还组建产业联盟扼杀后来者。

手段之一是低价倾销。这里面都是套路:一开始你没有,它通过垄断积累暴利;等你做出来,它马上降价倾销,让你越做越亏,暗无天日,最终断了产业化的念想。

当年液晶大战的惨痛就这样。三星、夏普等液晶巨头,一开始不愿在华建厂,而等深圳市政府组织国内彩电巨头,以及京东方要展开反攻时,他们却又主动跳出来求合作。结果导致长虹动摇并撤出,京东方则被晾在一边,国产计划泡汤。

享受这一“待遇”的,还有 MOCVD 设备商。MOCVD 是制造 LED 芯片的设备,在国产化之前,美、德两家巨头凭借垄断,每台设备卖 2000 万。而等国内厂商开始介入时,售价立刻暴跌至 600 万。结果,数十个国内玩家,如今只剩下中微、中晟光电等少数几家。

去年,高通在华推出重磅计划,与大唐电信旗下的联芯科技成立领盛科技,联手进军中低端芯片市场。关心国内芯片产业的很多人士都一致认为,这是高通要“借刀杀人”,要以领盛科技用价格战的方式绞杀正在向中高端芯片市场进军的紫光展讯。而 5 月 4 日的最新消息显示,这家合资企业已正式获批。

手段之二是发动专利战,拖住对手,打击下游客户信心。
手段之二是发动专利战,拖住对手,打击下游客户信心。

2000 年,张汝京出走台湾,在上海创建中芯国际。之后,他从台积电四处挖人,使得中芯短时间内迅速崛起。但厄运随之而来,老冤家张忠谋很快就发起了专利战。这场持续近七年的战争,最终的结果是中芯割地赔款,张汝京黯然出局。

台积电通过此举,成功拖住了中芯。而当初引进张汝京的江上舟,也在两年后辞世,死前一直惦记着中芯的未来。

同样的一幕发生在中微身上。2007 年,尹志尧领衔的中微刚推出自己的蚀刻机,就被老东家美国应用材料告上法庭。紧接着,另一巨头泛林火上浇油。好在中微从一开始就小心规避专利陷阱,最终赢得了诉讼。

但结果却不甚理想,中微不但赔上巨额的诉讼费,还赔上了下游客户的信心。再加上适逢经济危机,不得不暂时砍掉 MOCVD 业务。

很不友好的环境

事实上,中国很早就重视了半导体的大战。最早可追溯至上世纪 60 年代。但在后来的发展中,由于自身路径,国内产研环境,以及不友好的产业环境等多种原因,逐渐掉队。

中国半导体一直是在冒着敌人的炮火匍匐地前进。而今,敌人的炮火更是越来越凶猛,越来越密集。

西方一直有一个针对出口管制的制度安排,最早是 1949 年成立的巴黎统筹委员会,之后在 1996 年演变为瓦森纳协定。该协定包含军用、民用两份控制清单,目的是限制向相关国家出口敏感产品和技术,中国就属于被限制的对象。

过去几十年,国家一直在努力突破这种封锁。90 年代,先后批复 908/909 工程,时任领导人表态:砸锅卖铁也要把半导体搞上去。国务院则用财政赤字拨款。

然而,作为两项工程的产物,华晶、华虹等企业到国际上采购设备却遭到抵制,最终发展受限,一直未有大的突破。

2006 年以后,国家又搞了 01 和 02 专项。前者剑指核心电子器件、高端通用芯片和基础软件,俗称核高基;后者剑指 IC 制造和成套工艺。

近年来,两个专项相继开花结果,例如中微的蚀刻机已看齐世界水平,中芯国际的工艺已挺进到 28 纳米,但受限于不友好的产业环境,水平还是差强人意。

以光刻机为例,ASML 的 EUV 光刻机即将投入 7 纳米工艺,而国内最先进的量产水平是 90 纳米。之所以差距惊人,原因之一是买不到高水平的镜头和光源,这是光刻机的核心部件,而国内缺乏相关的技术。

坊间一直传说,瓦森纳协定禁止向中国出口高端光刻机。这种说法后来遭到 ASML 公司的否认,该公司声称,最快将于 2019 年在中国晶圆厂见到 EUV 光刻机。

但业内人士透露,在瓦森纳协定中,确实是有光刻机限售条款的,只不过每隔几年,条款就会作相应的调整。之所以调的原因也是,国产水平在不断提高,所谓的调整,顶多也就是“敌人”不断地根据我方的进展调整炮火的轻重与射程。

一个有意思的细节是,国内研究机构在相关技术上取得突破不久,ASML 公司就否认了禁售的传闻。

产业环境并不是唯一的障碍,来自国家层面的干预更加要命。

美国政府曾多次否决中国企业针对美企的收购行为,包括著名的紫光并购美光计划。最近更是制裁了中兴通讯,断供其芯片。

2016 年,国内某基金收购德国爱思强时,连 FBI 都跳出来施压,最终迫使德方放弃了交易。而据中兴员工透露,在中兴断芯事件前,FBI 就入驻了公司内部。

为钱一把辛酸泪

半导体是一个烧钱的行业。上世纪 90 年代,中央在财政非常拮据的情况下,特批了 40 亿元搞半导体。但这点钱只是杯水车薪。

向民间要投资,更遭到冷遇。

这个行业不但烧钱,而且周期长,技术更新快,你刚研发出来,别人已经开始打价格战。这意味着,前期要不断砸钱,还见不到水花。对民营资本而言,这是无法承受之痛。

中微董事长尹志尧 2004 年满腔热血回国创业,就遇到了这个难题。

为造蚀刻机,中微在短时间内,烧光了地方政府和自筹资金,只好四处筹钱续命。当时民间资本对这个行业缺乏了解,也缺乏意愿,尹的一腔热血只能碰一鼻子灰。

万般无奈之下,只好赴硅谷融资。两周内,十几家风投踏破门槛,愿意提供 5000 万美元。此情此景,令报国心切的尹志尧百感交集:难道只有美国造得出蚀刻机?

苦心积虑,只为国产化,绝不能大权旁落。在拿回几笔续命钱后,尹志尧继续寻找国内投资人。后来,在江上舟的引荐下,终于有国开行为中微背书。

跟中微有类似遭遇的,还有京东方。

做液晶面板 20 年,京东方一直伴随各种非议。尤其是它越亏越投的做法,更是被人质疑为绑架政府,因为其大部分资金来自银行和地方政府。

在资本市场上,由于不断增发,京东方背上了圈钱的骂名。但这些钱,大部分来自国资背景。其间,京东方曾多次拉私募基金入股,均被拒绝,理由是投资大,短期难见利。

幸运的是,苦熬多年后,京东方靓丽崛起,勇夺五个全球第一,让当初的坚持者赚得盆满钵满。

近年来,国家加大了对半导体行业的投入。大基金一期投入 1300 亿,已收尾;二期预计超过 2000 亿。乍一看,钱不少,但需要投资的项目也很多,涵盖芯片设计、制造、封测、设备等诸多领域。以一期为例,累计投资 62 个项目,涉及 23 家上市公司。

这样平均下来,每家获得的投资额并不多,跟撒胡椒面一样。

而纯靠市场手段去募集资金的难度同样非常大。以紫光为例,真正的大规模投资还没开始,市场就已不乏圈钱的质疑声,跟京东方当年恶战面板产业时所遭遇的挑战几乎没有两样。

显然,这样的投资强度是不够的。再看一组数据,就更能看出差距。

全球芯片三巨头,三星、英特尔、台积电,每年的投资都在百亿美元级别,而中芯国际不到对方的十分之一。

设备三巨头,应用材料、泛林、东京电子,每年在研发上投入5—10 亿美元不等,而中微半导体直到去年,收入才破 10 亿,还是人民币。

人才的切肤之痛

搞好半导体,主要靠三件事:一个是钱,一个是人,外加一个政策。钱的事好说,毕竟这些年国家不差钱;人的事很难搞,因为非一朝一夕之功。

数据显示,我国未来需要 70 万半导体人才,目前只有不到 30 万,缺口 40 万。

我们尤其缺行业的领军人物。这些年,01/02 专项取得的重大突破,很多都是海归创造的,他们长期任职于欧美半导体公司,拥有丰富的行业经验。

张汝京,德州仪器工作 20 年,在全球盖过 20 座芯片工厂。回国后,创办了中芯国际,以及国内第一家 12 寸硅晶圆厂,被誉为中国半导体之父。

尹志尧,闯荡硅谷 20 年,先后任职于英特尔、泛林和应用材料。回国后,创办中微半导体,几乎以一己之力,将国内介质蚀刻机带到了世界水平。

此外,曾任职于霍尼韦尔的姚力军,回国后做出了高纯度溅射靶材;美国留学归来的王淑敏,研发出国内第一款研磨液,打破了国外垄断。

日本、韩国和中国台湾地区,也是半导体人才的重要来源。大陆两大代工厂,中芯国际和厦门联芯,都有台湾背景,很多技术人员也来自台湾。

这几年,国内存储器的跨越式发展,也离不开日本、韩国、中国台湾技术人员的贡献。日本厂商尔必达破产后,大批日本人赴中国寻找机会,包括前社长坂本幸雄。

以紫光为例,外界看到其董事长赵伟国在资本和产业上动作频频,而事实上,他用心同样多的也是找人。其总投资预计 1000 亿美元左右的长江储存的执行董事长高启全,便是他费尽心思从台湾争抢过来的世界级半导体产业猛人。

目前,长江存储的 3D NAND 闪存已经获得第一笔订单,总计 10776 颗芯片,将用于 8GB USD 存储卡产品。今年 10 月,我国首批拥有完全自主知识产权的 32 层三维 NAND 闪存芯片将在这里实现量产,这也是中国集成电路闪存芯片产业规模化发展“零”的突破。

然而,引进人才毕竟不是长久之计。国内半导体行业要想大发展,必须立足于培养本土人才。

一方面,外来人才和本土人才,在利益、观念等方面是有冲突的。中芯国际在江上舟离世后,就陷入外来人员和本土人员的派系之争,一度影响到公司的发展。

另一方面,半导体是微加工行业,工艺很关键。很多外国技术人员之所以牛,是他们一辈子只干一件事积累起来的。

2002 年,上海微电子总经理赴德国考察,有工程师告诉他:“给你们全套图纸,也做不出来。”开始他不服,后来明白了。那里的抛光工人,祖孙三代干着同样一件事,“同样一个镜片,不同工人去磨,光洁度相差十倍。”

这恰是中国半导体行业的一个切肤之痛。

我们不缺设计人员,但缺工艺工程师,而这类人才很难靠引进来满足。

中芯国际之所以在制程上落后2-3 代,除了光刻机等设备受限外,工艺上的经验欠缺才是更重要的原因。

遗憾的是,目前国内不少高校的人才培养,与现实脱节。大多数学生跑去做软件,做应用,却不愿搞更基础的计算机系统和底层结构。

不容乐观的生态链

在自然界,动植物要生存,必须融入生物链。

做企业也一样。只不过,在企业这个生态链中,先行者有成本优势,再加上稳定可靠的供应链,使得他们能够持续盈利,进而支撑着技术的不断进步。

这对后来者而言,如同一道不可逾越的壁垒。

这些年,中国半导体产业面临的一大难题,就是如何融入这个生态链。

龙芯是一个很好的例子。这款中科院计算所自主研发的芯片,尽管性能不俗,但一直游离在民用市场外。原因很简单,市场上有更成熟、性价比更高的处理器。

龙芯的遭遇并非个案。大部分芯片制造厂,在采购装备时,一定考虑的是进口设备,因为国产设备刚起步,质量不稳定、一致性差。

当年,LED 芯片刚在国内兴起时,各大芯片厂均只认美、德设备,而地方政府的补贴也只给进口设备。

内忧外患,将国内 MOCVD 设备商逼到了绝境,“客户不太愿意用……因为不信任,需要重新验证,这又要花钱。”中晟光电负责人陈爱华说。

直到后来,工信部为每台国产设备提供 2000 万补贴,形势才开始好转。

新产品研制出来,没有人用,就不可能盈利;没有盈利,就没钱搞研发。结果只能是恶性循环,胎死腹中。

这个时候,需要来自生态链的支持。国内半导体行业近年来的进步,尤其是设备和材料领域,很大程度上得益于中芯国际、厦门联芯等晶圆制造厂的带动。

但这种机会,不是国外厂商所能提供的。那种一切交给市场的想法,不能说幼稚,至少也是罔顾事实的。

几年前,韩国 SK 海力士曾采购过中微半导体的蚀刻机,后来放弃了。表面上是因为性能不及预期,实际是担心泄露核心工艺的秘密。

一个有意思的细节是,尽管中国民用芯片九成依赖进口,但军用芯片却基本能自给自足,甚至还有出口。比如,龙芯就稳定运行在北斗导航系统上。

另一款自主芯片,来自总参 56 所的申威,则撑起了我们的太湖之光超级计算机。

军用很出彩,民用却卖不出去?问题就在生态链上。

军用市场是一个封闭的小圈子,产品追求稳定性和抗干扰,对性能并不敏感。龙芯和申威在这里能找到自己的位置。

反观民用市场,性能为王,技术迭代快,龙芯和申威很难融入这样的生态链。

结束语

通过以上梳理,我们看到,国内造不好高端芯片,有外部因素,也有自身原因。

形势看似悲观,前景却很光明。

一方面,半导体行业向中国转移的大趋势不会改变。另一方面,摩尔定律在工艺上逐渐趋近极限,客观上给了国内企业追赶的机会,而国家也正进一步加大支持和投入。

最近,我国国家领导人在武汉考察时,就特别到长江存储作了考察与指示。受此鼓舞的赵伟国则表示,公司将尽快在全球集成电路产业占据重要地位,用 5 到 10 年时间成为全球三维闪存主要供应商之一。

在国家的支持和企业的自身努力下,国内半导体产业链正在出现由点到面的突破,而在三大历史性机遇的支持下,我们也必须迎头赶上。

否则,后面的仗将会越来越难打,因为半导体产业不光是现代高科技产业的基础,更是支撑和保障国家安全的战略性、基础性和先导性产业,而且重要性会越来越大。

from:https://news.cnblogs.com/n/613348/