前言

学golang,我需要阅读一本go语言的书籍,也需要浏览和go相关的社区网站。

有一个问题是,为什么需要阅读一本编程书籍?直接从网上搜索是可以找到很多快餐资料的,似乎比书籍更有效?

答案是全面。通常,书的质量比博客高多了,我现在写的就是博客,算不上书籍。书籍的质量也体现在它的内容比较系统、全面。所以,读完之后,你能获得的一个好处是,查漏补缺,可以避免犯一些不该犯的错误。不要小瞧这一点,很多时候一个人犯的错误,仅仅是因为他不知道某个知识点,知识面上漏了一个点。而这是本应该避免的。也不要觉得这low,sql优化本身也是因为之前写sql的人犯错了,不是吗?大多数慢sql也都可以看做是一种错误,只是它在一开始的时候没有明显暴露出来。

网上推荐这本书的比较多,the go programming language ,俗称go语言圣经。会从书中摘录一些知识点记录下来。附带的,会记录部分从网站上了解到的知识点。但整体上以书籍为主。

 总体感受:

一、

golang和脚本语言很像,它和lua一样,支持语句末尾不写;分号,关键字import、func等,函数当做变量使用(当然,c里面函数指针,java里面函数接口,也是类似效果)。还有协程,lua里也有协程。

二、

golang好像一个规定了编码规范的c语言。每种语言都有编码规范,lua有,java有,c应该也有。一种语言在实际使用的过程中,我们总会要求一种工业规范,不同人的编码风格会有差异,但有必要去遵守一些的共同的标准,遵守这些标准可以减少不必要的风险。

比如,没有使用的包不要引入,多个变量更新的赋值语句a,b=b,a。比如,golang里对指针的规定,golang里和c一样有取地址操作&和取值操作*,但是不允许对指针做算术运算。再比如,golang里从语法上把自增自减规范成一条语句,而c里面也说过:“初学者学了++和--容易写一堆符号糅杂在一起,觉得自己写得好,但其实没必要,程序执行起来是一样的,把代码写得简洁明了就好”。所以,golang像是在简化里c里面的复杂概念并规避易错点。golang是对程序员友好的c语言。

golang相当于是从语法层面上硬性要求了c语言的编码规范,c语言加上代码规范就成了golang。

 

语法细节

 

fmt.Printf 输出格式

%d          十进制整数
%x, %o, %b  十六进制,八进制,二进制整数。
%f, %g, %e  浮点数: 3.141593 3.141592653589793 3.141593e+00
%t          布尔:true或false
%c          字符(rune) (Unicode码点)
%s          字符串
%q          带双引号的字符串"abc"或带单引号的字符'c'
%v          变量的自然形式(natural format)
%T          变量的类型
%%          字面上的百分号标志(无操作数)

 

go提交的是一条语句 go funcname()  这里的funcname()本身就是一条语句,语句可以是一条赋值语句,一个函数调用,一个计算例如a+b

goroutine是一种函数的并发执行方式,而channel是用来在goroutine之间进行参数传递。main函数本身也运行在一个goroutine中,而go function则表示创建一个新的goroutine,并在这个新的goroutine中执行这个函数。

当一个goroutine尝试在一个channel上做send或者receive操作时,这个goroutine会阻塞在调用处,直到另一个goroutine从这个channel里接收或者写入值,这样两个goroutine才会继续执行channel操作之后的逻辑。

 

尽量使用通道来实现同步,如果不得以需要竞态条件,go提供了mu

mu.Lock()
count++
mu.Unlock()

 

if err := r.ParseForm(); err != nil {
log.Print(err)
}

用if和ParseForm结合可以让代码更加简单,并且可以限制err这个变量的作用域,这么做是很不错的。

等价于

err := r.ParseForm()
if err != nil {
log.Print(err)
}

指针: Go语言提供了指针。指针是一种直接存储了变量的内存地址的数据类型。在其它语言中,比如C语言,指针操作是完全不受约束的。在另外一些语言中,指针一般被处理为“引用”,除了到处传递这些指针之外,并不能对这些指针做太多事情。Go语言在这两种范围中取了一种平衡。指针是可见的内存地址,&操作符可以返回一个变量的内存地址,并且*操作符可以获取指针指向的变量内容,但是在Go语言里没有指针运算,也就是不能像c语言里可以对指针进行加或减操作。我们会在2.3.2中进行详细介绍。

方法和接口: 方法是和命名类型关联的一类函数。Go语言里比较特殊的是方法可以被关联到任意一种命名类型。在第六章我们会详细地讲方法。接口是一种抽象类型,这种类型可以让我们以同样的方式来处理不同的固有类型,不用关心它们的具体实现,而只需要关注它们提供的方法。第七章中会详细说明这些内容。

 

 

Go语言主要有四种类型的声明语句:var、const、type和func,分别对应变量、常量、类型和函数实体对象的声明。

零值初始化机制可以确保每个声明的变量总是有一个良好定义的值,因此在Go语言中不存在未初始化的变量。

 数值类型变量对应的零值是0,布尔类型变量对应的零值是false,字符串类型对应的零值是空字符串,接口或引用类型(包括slice、指针、map、chan和函数)变量对应的零值是nil。数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值。

 

在函数内部,有一种称为简短变量声明语句的形式可用于声明和初始化局部变量。它以“名字 := 表达式”形式声明变量,变量的类型根据表达式来自动推导。

简短变量声明语句中必须至少要声明一个新的变量,简短变量声明左边的变量可能并不是全部都是刚刚声明的。

f, err := os.Open(name)
if err != nil {
return err
}
// ...use f...
f.Close()

 

go中关于指针的设计

一个指针的值是另一个变量的地址。一个指针对应变量在内存中的存储位置。并不是每一个值都会有一个内存地址,但是对于每一个变量必然有对应的内存地址。

在Go语言中,返回函数中局部变量的地址也是安全的。例如下面的代码,调用f函数时创建局部变量v,在局部变量地址被返回之后依然有效,因为指针p依然引用这个变量。

每次我们对一个变量取地址,或者复制指针,我们都是为原变量创建了新的别名。

 

局部变量的生命周期则是动态的:每次从创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。

函数的参数变量和返回值变量都是局部变量。它们在函数每次被调用的时候创建。

 

编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但可能令人惊讶的是,这个选择并不是由用var还是new声明变量的方式决定的。

关于内存分配,从函数逃逸的局部变量必定在堆上分配,非逃逸的变量可以在栈上或者堆上分配。逃逸的变量会分配额外的内存,是一个可以优化的点。

数值变量也可以支持++递增和--递减语句(译注:自增和自减是语句,而不是表达式,因此x = i++之类的表达式是错误的):

 

元组赋值是另一种形式的赋值语句,它允许同时更新多个变量的值。在赋值之前,赋值语句右边的所有表达式将会先进行求值,然后再统一更新左边对应变量的值。这对于处理有些同时出现在元组赋值语句左右两边的变量很有帮助,例如我们可以这样交换两个变量的值:

x, y = y, x

a[i], a[j] = a[j], a[i]

 

函数多返回值

可赋值性的规则对