ch1: 规范图像文件编号
@ -4,9 +4,9 @@ Go语言最初由Google公司的Robert Griesemer、Ken Thompson和Rob Pike三个
|
|||||||
|
|
||||||
Go语言很多时候被描述为“类C语言”,或者是“21世纪的C语言”。从各种角度看,Go语言确实是从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等诸多编程思想,还有彻底继承和发扬了C语言简单直接的暴力编程哲学等。图1-1是《Go语言圣经》中给出的Go语言的基因图谱,我们可以从中看到有哪些编程语言对Go语言产生了影响。
|
Go语言很多时候被描述为“类C语言”,或者是“21世纪的C语言”。从各种角度看,Go语言确实是从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等诸多编程思想,还有彻底继承和发扬了C语言简单直接的暴力编程哲学等。图1-1是《Go语言圣经》中给出的Go语言的基因图谱,我们可以从中看到有哪些编程语言对Go语言产生了影响。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图 1.1-1 Go语言基因族谱*
|
*图 1-1 Go语言基因族谱*
|
||||||
|
|
||||||
首先看基因图谱的左边一支。可以明确看出Go语言的并发特性是由贝尔实验室的Hoare于1978年发布的CSP理论演化而来。其后,CSP并发模型在Squeak/NewSqueak和Alef等编程语言中逐步完善并走向实际应用,最终这些设计经验被消化并吸收到了Go语言中。业界比较熟悉的Erlang编程语言的并发编程模型也是CSP理论的另一种实现。
|
首先看基因图谱的左边一支。可以明确看出Go语言的并发特性是由贝尔实验室的Hoare于1978年发布的CSP理论演化而来。其后,CSP并发模型在Squeak/NewSqueak和Alef等编程语言中逐步完善并走向实际应用,最终这些设计经验被消化并吸收到了Go语言中。业界比较熟悉的Erlang编程语言的并发编程模型也是CSP理论的另一种实现。
|
||||||
|
|
||||||
@ -24,17 +24,17 @@ CSP并发模型最经典的实际应用是来自爱立信发明的Erlang编程
|
|||||||
|
|
||||||
图1-2展示了Go语言库早期代码库日志可以看出最直接的演化历程(Git用`git log --before={2008-03-03} --reverse`命令查看)。
|
图1-2展示了Go语言库早期代码库日志可以看出最直接的演化历程(Git用`git log --before={2008-03-03} --reverse`命令查看)。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图 1.1-2 Go语言开发日志*
|
*图 1-2 Go语言开发日志*
|
||||||
|
|
||||||
从早期提交日志中也可以看出,Go语言是从Ken Thompson发明的B语言、Dennis M. Ritchie发明的C语言逐步演化过来的,它首先是C语言家族的成员,因此很多人将Go语言称为21世纪的C语言。
|
从早期提交日志中也可以看出,Go语言是从Ken Thompson发明的B语言、Dennis M. Ritchie发明的C语言逐步演化过来的,它首先是C语言家族的成员,因此很多人将Go语言称为21世纪的C语言。
|
||||||
|
|
||||||
图1-3是Go语言中来自贝尔实验室特有并发编程基因的演化过程:
|
图1-3是Go语言中来自贝尔实验室特有并发编程基因的演化过程:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图 1.1-3 Go语言并发演化历史*
|
*图 1-3 Go语言并发演化历史*
|
||||||
|
|
||||||
纵观整个贝尔实验室的编程语言的发展进程,从B语言、C语言、Newsqueak、Alef、Limbo语言一路走来,Go语言继承了来着贝尔实验室的半个世纪的软件设计基因,终于完成了C语言革新的使命。纵观这几年来的发展趋势,Go语言已经成为云计算、云存储时代最重要的基础编程语言。
|
纵观整个贝尔实验室的编程语言的发展进程,从B语言、C语言、Newsqueak、Alef、Limbo语言一路走来,Go语言继承了来着贝尔实验室的半个世纪的软件设计基因,终于完成了C语言革新的使命。纵观这几年来的发展趋势,Go语言已经成为云计算、云存储时代最重要的基础编程语言。
|
||||||
|
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
在创世纪章节中我们简单介绍了Go语言的演化基因族谱,对其中来自于贝尔实验室的特有并发编程基因做了重点介绍,最后引出了Go语言版的“Hello, World”程序。其实“Hello, World”程序是展示各种语言特性的最好的例子,是通向该语言的一个窗口。这一节我们将沿着各个编程语言演化的时间轴,简单回顾下“Hello, World”程序是如何逐步演化到目前的Go语言形式、最终完成它的革命使命的。
|
在创世纪章节中我们简单介绍了Go语言的演化基因族谱,对其中来自于贝尔实验室的特有并发编程基因做了重点介绍,最后引出了Go语言版的“Hello, World”程序。其实“Hello, World”程序是展示各种语言特性的最好的例子,是通向该语言的一个窗口。这一节我们将沿着各个编程语言演化的时间轴,简单回顾下“Hello, World”程序是如何逐步演化到目前的Go语言形式、最终完成它的革命使命的。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图 1.2-1 Go语言并发演化历史*
|
*图 1-4 Go语言并发演化历史*
|
||||||
|
|
||||||
## 1.2.1 B语言 - Ken Thompson, 1972
|
## 1.2.1 B语言 - Ken Thompson, 1972
|
||||||
|
|
||||||
@ -102,9 +102,9 @@ print("Hello,", "World", "\n");
|
|||||||
|
|
||||||
从上面的程序中,除了猜测`print`函数可以支持多个参数外,我们很难看到Newsqueak语言相关的特性。由于Newsqueak语言和Go语言相关的特性主要是并发和管道。因此,我们这里通过一个并发版本的“素数筛”算法来略窥Newsqueak语言的特性。“素数筛”的原理如图:
|
从上面的程序中,除了猜测`print`函数可以支持多个参数外,我们很难看到Newsqueak语言相关的特性。由于Newsqueak语言和Go语言相关的特性主要是并发和管道。因此,我们这里通过一个并发版本的“素数筛”算法来略窥Newsqueak语言的特性。“素数筛”的原理如图:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图 1.2-2 素数筛*
|
*图 1-5 素数筛*
|
||||||
|
|
||||||
Newsqueak语言并发版本的“素数筛”程序如下:
|
Newsqueak语言并发版本的“素数筛”程序如下:
|
||||||
|
|
||||||
@ -164,9 +164,9 @@ Newsqueak语言中并发体和管道的语法和Go语言已经比较接近了,
|
|||||||
|
|
||||||
由于Alef语言同时支持进程和线程并发体,而且在并发体中可以再次启动更多的并发体,导致了Alef的并发状态会异常复杂。同时Alef没有自动垃圾回收机制(Alef因为保留的C语言灵活的指针特性,也导致了自动垃圾回收机制实现比较困难),各种资源充斥于不同的线程和进程之间,导致并发体的内存资源管理异常复杂。Alef语言全部继承了C语言的语法,可以认为是增强了并发语法的C语言。下图是Alef语言文档中展示的一个可能的并发体状态:
|
由于Alef语言同时支持进程和线程并发体,而且在并发体中可以再次启动更多的并发体,导致了Alef的并发状态会异常复杂。同时Alef没有自动垃圾回收机制(Alef因为保留的C语言灵活的指针特性,也导致了自动垃圾回收机制实现比较困难),各种资源充斥于不同的线程和进程之间,导致并发体的内存资源管理异常复杂。Alef语言全部继承了C语言的语法,可以认为是增强了并发语法的C语言。下图是Alef语言文档中展示的一个可能的并发体状态:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图 1.2-3 Alef并发模型*
|
*图 1-6 Alef并发模型*
|
||||||
|
|
||||||
Alef语言并发版本的“Hello World”程序如下:
|
Alef语言并发版本的“Hello World”程序如下:
|
||||||
|
|
||||||
|
@ -27,9 +27,9 @@ var d = [...]int{1, 2, 4: 5, 6} // 定义一个长度为6的int类型数组, 元
|
|||||||
|
|
||||||
数组的内存结构比较简单。比如下面是一个`[4]int{2,3,5,7}`数组值对应的内存结构:
|
数组的内存结构比较简单。比如下面是一个`[4]int{2,3,5,7}`数组值对应的内存结构:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图 1.3-1 数组布局*
|
*图 1-7 数组布局*
|
||||||
|
|
||||||
|
|
||||||
Go语言中数组是值语义。一个数组变量即表示整个数组,它并不是隐式的指向第一个元素的指针(比如C语言的数组),而是一个完整的值。当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。如果数组较大的话,数组的赋值也会有较大的开销。为了避免复制数组带来的开销,可以传递一个指向数组的指针,但是数组指针并不是数组。
|
Go语言中数组是值语义。一个数组变量即表示整个数组,它并不是隐式的指向第一个元素的指针(比如C语言的数组),而是一个完整的值。当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。如果数组较大的话,数组的赋值也会有较大的开销。为了避免复制数组带来的开销,可以传递一个指向数组的指针,但是数组指针并不是数组。
|
||||||
@ -161,9 +161,9 @@ type StringHeader struct {
|
|||||||
|
|
||||||
我们可以看看字符串“Hello, world”本身对应的内存结构:
|
我们可以看看字符串“Hello, world”本身对应的内存结构:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图 1.3-2 字符串布局*
|
*图 1-8 字符串布局*
|
||||||
|
|
||||||
|
|
||||||
分析可以发现,“Hello, world”字符串底层数据和以下数组是完全一致的:
|
分析可以发现,“Hello, world”字符串底层数据和以下数组是完全一致的:
|
||||||
@ -214,9 +214,9 @@ fmt.Println("\xe7\x95\x8c") // 打印: 界
|
|||||||
|
|
||||||
下图展示了“Hello, 世界”字符串的内存结构布局:
|
下图展示了“Hello, 世界”字符串的内存结构布局:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图 1.3-3 字符串布局*
|
*图 1-9 字符串布局*
|
||||||
|
|
||||||
Go语言的字符串中可以存放任意的二进制字节序列,而且即使是UTF8字符序列也可能会遇到坏的编码。如果遇到一个错误的UTF8编码输入,将生成一个特别的Unicode字符‘\uFFFD’,这个字符在不同的软件中的显示效果可能不太一样,在印刷中这个符号通常是一个黑色六角形或钻石形状,里面包含一个白色的问号‘<E58FB7>’。
|
Go语言的字符串中可以存放任意的二进制字节序列,而且即使是UTF8字符序列也可能会遇到坏的编码。如果遇到一个错误的UTF8编码输入,将生成一个特别的Unicode字符‘\uFFFD’,这个字符在不同的软件中的显示效果可能不太一样,在印刷中这个符号通常是一个黑色六角形或钻石形状,里面包含一个白色的问号‘<E58FB7>’。
|
||||||
|
|
||||||
@ -368,9 +368,9 @@ type SliceHeader struct {
|
|||||||
|
|
||||||
可以看出切片的开头部分和Go字符串是一样的,但是切片多了一个`Cap`成员表示切片指向的内存空间的最大容量(对应元素的个数,而不是字节数)。下图是`x := []int{2,3,5,7,11}`和`y := x[1:3]`两个切片对应的内存结构。
|
可以看出切片的开头部分和Go字符串是一样的,但是切片多了一个`Cap`成员表示切片指向的内存空间的最大容量(对应元素的个数,而不是字节数)。下图是`x := []int{2,3,5,7,11}`和`y := x[1:3]`两个切片对应的内存结构。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图 1.3-4 切片布局*
|
*图 1-10 切片布局*
|
||||||
|
|
||||||
|
|
||||||
让我们看看切片有哪些定义方式:
|
让我们看看切片有哪些定义方式:
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
Go语言程序的初始化和执行总是从`main.main`函数开始的。但是如果`main`包导入了其它的包,则会按照顺序将它们包含进`main`包里(这里的导入顺序依赖具体实现,一般可能是以文件名或包路径名的字符串顺序导入)。如果某个包被多次导入的话,在执行的时候只会导入一次。当一个包被导入时,如果它还导入了其它的包,则先将其它的包包含进来,然后创建和初始化这个包的常量和变量,再调用包里的`init`函数,如果一个包有多个`init`函数的话,调用顺序未定义(实现可能是以文件名的顺序调用),同一个文件内的多个`init`则是以出现的顺序依次调用(`init`不是普通函数,可以定义有多个,所以也不能被其它函数调用)。最后,当`main`包的所有包级常量、变量被创建和初始化完成,并且`init`函数被执行后,才会进入`main.main`函数,程序开始正常执行。下图是Go程序函数启动顺序的示意图:
|
Go语言程序的初始化和执行总是从`main.main`函数开始的。但是如果`main`包导入了其它的包,则会按照顺序将它们包含进`main`包里(这里的导入顺序依赖具体实现,一般可能是以文件名或包路径名的字符串顺序导入)。如果某个包被多次导入的话,在执行的时候只会导入一次。当一个包被导入时,如果它还导入了其它的包,则先将其它的包包含进来,然后创建和初始化这个包的常量和变量,再调用包里的`init`函数,如果一个包有多个`init`函数的话,调用顺序未定义(实现可能是以文件名的顺序调用),同一个文件内的多个`init`则是以出现的顺序依次调用(`init`不是普通函数,可以定义有多个,所以也不能被其它函数调用)。最后,当`main`包的所有包级常量、变量被创建和初始化完成,并且`init`函数被执行后,才会进入`main.main`函数,程序开始正常执行。下图是Go程序函数启动顺序的示意图:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图 1.4-1 包初始化流程*
|
*图 1-11 包初始化流程*
|
||||||
|
|
||||||
|
|
||||||
要注意的是,在`main.main`函数执行之前所有代码都运行在同一个goroutine,也就是程序的主系统线程中。因此,如果某个`init`函数内部用go关键字启动了新的goroutine的话,新的goroutine只有在进入`main.main`函数之后才可能被执行到。
|
要注意的是,在`main.main`函数执行之前所有代码都运行在同一个goroutine,也就是程序的主系统线程中。因此,如果某个`init`函数内部用go关键字启动了新的goroutine的话,新的goroutine只有在进入`main.main`函数之后才可能被执行到。
|
||||||
|
@ -259,9 +259,9 @@ func main() {
|
|||||||
|
|
||||||
Go程序的初始化和执行总是从`main.main`函数开始的。但是如果`main`包里导入了其它的包,则会按照顺序将它们包含进`main`包里(这里的导入顺序依赖具体实现,一般可能是以文件名或包路径名的字符串顺序导入)。如果某个包被多次导入的话,在执行的时候只会导入一次。当一个包被导入时,如果它还导入了其它的包,则先将其它的包包含进来,然后创建和初始化这个包的常量和变量。然后就是调用包里的`init`函数,如果一个包有多个`init`函数的话,实现可能是以文件名的顺序调用,同一个文件内的多个`init`则是以出现的顺序依次调用(`init`不是普通函数,可以定义有多个,所以不能被其它函数调用)。最终,在`main`包的所有包常量、包变量被创建和初始化,并且`init`函数被执行后,才会进入`main.main`函数,程序开始正常执行。下图是Go程序函数启动顺序的示意图:
|
Go程序的初始化和执行总是从`main.main`函数开始的。但是如果`main`包里导入了其它的包,则会按照顺序将它们包含进`main`包里(这里的导入顺序依赖具体实现,一般可能是以文件名或包路径名的字符串顺序导入)。如果某个包被多次导入的话,在执行的时候只会导入一次。当一个包被导入时,如果它还导入了其它的包,则先将其它的包包含进来,然后创建和初始化这个包的常量和变量。然后就是调用包里的`init`函数,如果一个包有多个`init`函数的话,实现可能是以文件名的顺序调用,同一个文件内的多个`init`则是以出现的顺序依次调用(`init`不是普通函数,可以定义有多个,所以不能被其它函数调用)。最终,在`main`包的所有包常量、包变量被创建和初始化,并且`init`函数被执行后,才会进入`main.main`函数,程序开始正常执行。下图是Go程序函数启动顺序的示意图:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图 1.5-1 包初始化流程*
|
*图 1-12 包初始化流程*
|
||||||
|
|
||||||
要注意的是,在`main.main`函数执行之前所有代码都运行在同一个Goroutine中,也是运行在程序的主系统线程中。如果某个`init`函数内部用go关键字启动了新的Goroutine的话,新的Goroutine和`main.main`函数是并发执行的。
|
要注意的是,在`main.main`函数执行之前所有代码都运行在同一个Goroutine中,也是运行在程序的主系统线程中。如果某个`init`函数内部用go关键字启动了新的Goroutine的话,新的Goroutine和`main.main`函数是并发执行的。
|
||||||
|
|
||||||
|
@ -416,9 +416,9 @@ func main() {
|
|||||||
|
|
||||||
在“Hello world 的革命”一节中,我们为了演示Newsqueak的并发特性,文中给出了并发版本素数筛的实现。并发版本的素数筛是一个经典的并发例子,通过它我们可以更深刻地理解Go语言的并发特性。“素数筛”的原理如图:
|
在“Hello world 的革命”一节中,我们为了演示Newsqueak的并发特性,文中给出了并发版本素数筛的实现。并发版本的素数筛是一个经典的并发例子,通过它我们可以更深刻地理解Go语言的并发特性。“素数筛”的原理如图:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图 1.6-1 素数筛*
|
*图 1-13 素数筛*
|
||||||
|
|
||||||
|
|
||||||
我们需要先生成最初的`2, 3, 4, ...`自然数序列(不包含开头的0、1):
|
我们需要先生成最初的`2, 3, 4, ...`自然数序列(不包含开头的0、1):
|
||||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |