贝尔实验室的Tony Hoare于1978年发表的鲜为外界所知的关于并发研究的基础文献《顺序通信进程》(communicating sequential processes, 缩写为CSP)。在CSP中,程序是一组中间没有共享状态的平行运行的处理过程,它们之间使用管道进行通信和控制同步。
正如Rob Pike所说,“软件的复杂性是乘法级相关的”,通过增加一个部分的复杂性来修复问题通常将慢慢地增加其他部分的复杂性。通过增加功能和选项和配置是修复问题的最快的途径,但是这很容易让人忘记简洁的内涵。即使从长远来看,简洁依然是好软件的关键因素。
简洁的设计需要在工作开始的时候舍弃不必要的想法,并且在软件的生命周期内严格区别好的改变或坏的改变。通过足够的努力,一个好的改变可以在不破坏原有完整概念的前提下保持自适应,正如Fred Brooks所说的“概念完整性”;而一个坏的改变则不能达到这个效果,它们仅仅是通过肤浅的和简单的妥协来破坏原有设计的一致性。只有通过简洁的设计,才能让一个系统保持稳定、安全和持续的进化。
Go目前不支持的特性:没有隐式的数值转换,没有构造函数和析构函数,没有运算符重载,没有默认参数,也没有继承,没有泛型,没有异常,没有宏,没有函数修饰,更没有线程局部存储。
Go语言提供了基于CSP的并发特性支持。Go语言的动态栈使得轻量级线程goroutine的初始栈可以很小,因此创建一个goroutine的代价很小,创建百万级的goroutine完全是可行的。
按照惯例,我们在每个包的包声明前添加注释(该注释是源文件的文档);对于main package,注释包含一句或几句话,从整体角度对程序做个描述。
在每一个函数之前写一个说明函数行为的注释也是一个好习惯。这些惯例很重要,因为这些内容会被像godoc这样的工具检测到,并且在执行命令时显示这些注释。
// Hello prints "Hello, World!"
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello, World!")
}
s := "" : 短变量声明,最简洁,但只能用在函数内部,而不能用于包变量
var s string : 依赖于字符串的默认初始化零值机制,被初始化为""
var args []string
var argsOut
// 低效
for _, arg := range args {
argsOut += arg
}
// 高效很多,len(args)越大越显著
import "strings"
argsOut ;= strings.Join(args, "")
理论上函数和包级别的变量(package-level entities)可以任意顺序声明,并不影响其被调用。
Go语言并不需要显式地在每一个case后写break,语言默认执行完 case后的逻辑语句会自动退出。如果你想要相邻的几个case都执行同一逻辑的话,,需要自己显式地写上一个fallthrough语句来覆盖这种默认行为。
Go指针是可见的内存地址,&操作符可以返回一个变量的内存地址,并且*操作符可以获取指针指向的变量内容,但是在Go语言里没有指针运算,也就是不能像c语言里可以对 指针进行加或减操作。
Go语言程序员推荐使用 驼峰式 命名,当名字有几个单词组成的时优先使用大小写分隔,而不是优先用下划线分隔。而像ASCII和HTML这样的缩略词则避免使用大小写混合的写法。
包一级的各种类型的声明语句的顺序无关紧要(函数内部的名字则必须先声明之后才能使用)。在包一级声明语句声明的名字可在整个包对应的每个源文件中访问,而不是仅仅在其声明语句所在的源文件中访问。相比之下,局部声明的名字就只能在函数内部很小的范围被访问。在包级别声明的变量会在main入口函数执行前完成初始化,局部变量将在声明语句被执行到的时候完成初始化。
var name type = expression
如果初始化表达式被省略,那么将用零值初始化该变量。 数值类型变量对应的零值是0,布尔类型变量对应的零值是false,字符串类型对应的零值是空字符串,接口或引用类型(包括slice、map、chan和函数)变量对应的零值是nil,数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值。零值初始化机制可以确保每个声明的变量总是有一个良好定义的值,因此在Go语言中不存在 未初始化的变量。
简短变量声明被广泛用于大部分的局部变量的声明和初始化。简短变量声明语句中必须至少要声明一个新的变量,否则代码将不能编译通过。简短变量声明语句只有对已经在同级词法域声明过的变量才和赋值操作语句等价,如果变量是在外部词法域声明的,那么简短变量声明语句将会在当前词法域重新声明一个新的变量。
var形式的声明语句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。
在Go语言中,返回函数中局部变量的地址也是安全的,局部变量地址被返回之后依然有效。
指针特别有价值的地方在于我们可以不用名字而访问一个变量,但是这是一把双刃剑:要找到一个变量的所有访问者并不容易,我们必须知道变量全部变量的别名(这是Go语言的垃圾回收器所做的工作)。不仅仅是指针会创建别名,很多其他引用类型也会创建别名,例如slice、map和chan,甚至结构体、数组和接口都会创建所引用变量的别名。
表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为 *T 。用new创建变量和普通变量声明语句方式创建变量没有什么区别,除了不需要声明一个临时变量的名字外。换言之,new函数类似是一种语法糖。new函数使用常见相对比较少,因为对应结构体来说,可以直接用字面量语法创建新变量的方法会更灵活。
变量的生命周期指的是在程序运行期间变量有效存在的时间间隔。对于在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。而相比之下,在局部变量的声明周期则是动态的:从每次创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。
为了防止编译器在行尾自动插入分号而导致的编译错误,可以在末尾的参数变量后面显式插入逗号。最后插入的逗号不会导致编译错误,这是Go编译器的一个特性。
编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但可能令人惊讶的是,这个选择并不是由用var还是new声明变量的方式决定的。其实在任何 时候,你并不需为了编写正确的代码而要考虑变量的逃逸行为,要记住的是,逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响。Go语言的自动垃圾收集器对编写正确的代码是一个巨大的帮助,但也并不是说你完全不用考 虑内存了。你虽然不需要显式地分配和释放内存,但是要编写高效的程序你依然需要了解变量的生命周期。
支持元组赋值:x, y = y, x
对于每一个类型T,都有一个对应的类型转换操作T(x),用于将x转为T类型。只有当两个类型的底层基础类型 相同时,才允许这种转型操作,或者是两者都是指向相同底层结构的指针类型,这些转换只改变类型而不会影响值本身。比较运算符 == 和 < 也可以用来比较一个命名类型的变量和另一个有相同类型的变量,或有着相同底层类型的未命名类型的值之间做比较。但是如果两个值有着不同的类型,则不能直 接进行比较。
一个包通常只有一个源文件有包注释(如果有多个包注释,目前的文档工具会根据源文件名的先后顺序将它们链接为一个包注释)。如果包注释很大,通常会放到一个独立的doc.go文件中。
每个文件都可以包含多个init初始化函数,按照它们声明的顺序被自动调用。init初始化函数除了不能被调用或引用外,其他行为和普通函数类似。每个包在解决依赖的前提下,以导入声明的顺序初始化,每个包只会被初始化一次。
要复杂处理的初始化,可以通过将初始化逻辑包装为一个匿名函数处 理。
Go语言将数据类型分为四类:基础类型、复合类型、引用类型和接口类型。
基础类型:数字、字符串和布尔型。
复合数据类型:数组和结构体。通过组合简单类型,来表达更加复杂的数据结构。
引用类型:指针、切片、字典、函数和通道。它们都是对程序中一个变量或状态的间接引用。这意味着对任一引用类型数据的修改都会影响所有该引用的拷贝。
无符号数往往只有在位运算或其它特殊的运算场景才会使用,就像bit集合、 分析二进制文件格式或者是哈希和加密操作等。它们通常并不用于仅仅是表达非负数量的场合(比如,数组的长度int not unit)。
通常应该优先使用float64类型,因为float32类型的累计计算误差很容易扩散。
布尔值并不会隐式转换为数字值0或1
一个字符串是一个不可改变的字节序列。字符串可以包含任意的数据。不变性意味如果两个字符串共享相同的底层数据的话也是安全的,这使得复制任何长度的字符串代价是低廉的。同样,一个字符串s和对应的字符串切片的操作也可以安全地共享相同的内存,因此字符串切片操作代价也是低廉的。
看CSP论文
网友评论