go

来源:互联网 发布:淘宝店铺描述怎么写 编辑:程序博客网 时间:2024/04/28 14:02

https://tour.go-zh.org/

每个 Go 程序都是由包组成的。

程序运行的入口是包 main 。

导出名

首字母大写的名称是被导出的。

短声明变量

在函数中, := 简洁赋值语句在明确类型的地方,可以用于替代 var 定义。

函数外的每个语句都必须以关键字开始( var 、 func 、等等), := 结构不能使用在函数外

数值常量

数值常量是高精度的  。

一个未指定类型的常量由上下文来决定其类型。

if 和 else


func pow(x, n, lim float64) float64 {}

func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}

两个 pow 调用都在 main 调用 fmt.Println 前执行完毕了。

switch

以 fallthrough 语句结束会继续运行下一个case


defer

defer 语句会延迟函数的执行直到上层函数返回。

延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用。

https://blog.go-zh.org/defer-panic-and-recover

结构体指针

通过指针间接的访问是透明的。


《go语言圣经》

1.1 import声明必须跟在文件的package声明之后。在import语句之后,则是各种方法、变量、常量、类型的声明语句(分别用关键字func, var, const, type来进行定义)。

Go语言是一门不需要分号作为语句或者声明结束的语言,除非要在一行中将多个语句、声明隔开。然而在编译时,编译器会主动在一些特定的符号(译注:比如行末是,一个标识符、一个整数、浮点数、虚数、字符或字符串文字、关键字break、continue、fallthrough或return中的一个、运算符和分隔符++、--、)、]或}中的一个) 后添加分号,所以在哪里加分号合适是取决于Go语言代码的。例如:在Go语言中的函数声明和 { 大括号必须在同一行,而在x + y这样的表达式中,在+号后换行可以,但是在+号前换行则会有问题(译注:以+结尾的话不会被插入分号分隔符,但是以x结尾的话则会被分号分隔符,从而导致编译错误)。

1.2 i++在Go语言里是语句,而不像C系的其它语言里是表达式。所以在Go语言里j = i++是非法的.而且++和--都只能放在变量名后面

空白标识符可以在任何你需要接收自己不想处理的值时使用。

而目标字符串的旧的字面值在得到新值以后就失去了用处,这些临时值会被Go语言的垃圾收集器干掉。



1.7 

http.HandleFunc("/", handler)    http.HandleFunc("/count", counter)
如果你的请求pattern是以/结尾,那么所有以该url为前缀的url都会被这条规则匹配。在这些代码的背后,服务器每一次接收请求处理时都会另起一个goroutine,这样服务器就可以同一时间处理多数请求。

go不能隐式转换类型。常量为值不属于某个类型,但不能把质量高的值赋给质量低的类型的变量,会报错truncate

2.3
var s string
var s = "ddd"
var b, f, s = true, 2.3, "four" 
函数内 s := "ddd"
在包级别声明的变量会在main入口函数执行前完成初始化(§2.6.2),局部变量将在声明语句被执行到的时候完成初始化。
请记住“:=”是一个变量声明语句,而“=‘是一个变量赋值操作。“:=”是用来在函数内代替var的。
简短变量声明语句只有对已经在同级词法域声明过的变量才和赋值操作语句等价,如果变量是在外部词法域声明的,那么简短变量声明语句将会在当前词法域重新声明一个新的变量。我们在本章后面将会看到类似的例子。
2.32 指针
变量有时候被称为可寻址的值。即使变量由表达式临时生成,那么表达式也必须能接受&取地址操作。
在Go语言中,返回函数中局部变量的地址也是安全的。
2.33 new
由于new只是一个预定义的函数,它并不是一个关键字,因此我们可以将new名字重新定义为别的类型。
2.34变量的生命周期
因为一个变量的有效周期只取决于是否可达,因此一个循环迭代内部的局部变量的生命周期可能超出其局部作用域。同时,局部变量可能在函数返回之后依然存在。
编译器会自动选择在栈上还是在堆上分配局部变量的存储空间
2.6 包和文件
初始化工作是自下而上进行的,main包最后被初始化。

译注:对于pc这类需要复杂处理的初始化,可以通过将初始化逻辑包装为一个匿名函数处理,像下面这样:

// pc[i] is the population count of i.var pc [256]byte = func() (pc [256]byte) {    for i := range pc {        pc[i] = pc[i/2] + byte(i&1)    }}()
2.7作用域
对于内置的类型、函数和常量,比如int、len和true等是在全局作用域的,因此可以在整个程序中直接使用。任何在在函数外部(也就是包级语法域)声明的名字可以在同一个包的任何源文件中访问的。对于导入的包,例如tempconv导入的fmt包,则是对应源文件级的作用域,因此只能在当前的文件中访问导入的fmt包,当前包的其它源文件无法访问在当前源文件导入的包。
花括弧包含的是显式的部分是for的循环体部分词法域,另外一个隐式的部分则是循环的初始化部分,比如用于迭代变量i的初始化。隐式的词法域部分的作用域还包含条件测试部分和循环后的迭代部分(i++),当然也包含循环体词法域。控制语句的隐式作用域在花括号之外,外层之内。
3 基本数据类型
Go语言将数据类型分为四类:基础类型、复合类型、引用类型和接口类型。
基本类型:数字,布尔,字符串
复合类型:数组,结构体
引用类型:指针,切片,字典,函数,通道。它们都是对程序中一个变量或状态的间接引用。这意味着对任一引用类型数据的修改都会影响所有该引用的拷贝。
3.1 整型

Unicode字符rune类型是和int32等价的类型,通常用于表示一个Unicode码点。这两个名称可以互换使用。同样byte也是uint8类型的等价类型,byte类型一般用于强调数值是一个原始的数据而不是一个小的整数。

最后,还有一种无符号的整数类型uintptr,没有指定具体的bit大小但是足以容纳指针。uintptr类型只有在底层编程是才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。

fmt %之后的[1]副词告诉Printf函数再次使用第一个操作数。第二,%后的#副词告诉Printf在用%o、%x或%X输出时生成0、0x或0X前缀。
3.5字符串
因为字符串是不可修改的,因此尝试修改字符串内部数据的操作也是被禁止的
不变性意味如果两个字符串共享相同的底层数据的话也是安全的,这使得复制任何长度的字符串代价是低廉的。同样,一个字符串s和对应的子字符串切片s[7:]的操作也可以安全地共享相同的内存,因此字符串切片操作代价也是低廉的。

3.5.4. 字符串和Byte切片

因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制。在这种情况下,使用bytes.Buffer类型将会更有效
4.2 slice
一个slice是一个轻量级的数据结构,提供了访问数组子序列(或者全部)元素的功能,而且slice的底层确实引用一个数组对象。一个slice由三个部分构成:指针、长度和容量。
长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。
多个slice之间可以共享底层的数据,并且引用的数组部分区间可能重叠。
x[m:n]切片操作对于字符串则生成一个新字符串,如果x是[]byte的话则生成一个新的[]byte。
数组声明指明长度或用...,slice声明什么都不写[]
slice声明会隐式地创建一个合适大小的数组,然后slice的指针指向底层的数组。
slice比较:只有bytes.Equal函数来判断两个字节型slice是否相等([]byte),其他自己比。

但是为何slice不直接支持比较运算符呢?这方面有两个原因。第一个原因,一个slice的元素是间接引用的,一个slice甚至可以包含自身。虽然有很多办法处理这种情形,但是没有一个是简单有效的。

第二个原因,因为slice的元素是间接引用的,一个固定值的slice在不同的时间可能包含不同的元素,因为底层数组的元素可能会被修改。并且Go语言中map等哈希表之类的数据结构的key只做简单的浅拷贝,它要求在整个声明周期中相等的key必须对相同的元素。对于像指针或chan之类的引用类型,==相等测试可以判断两个是否是引用相同的对象。一个针对slice的浅相等测试的==操作符可能是有一定用处的,也能临时解决map类型的key问题,但是slice和数组不同的相等测试行为会让人困惑。因此,安全的做饭是直接禁止slice之间的比较操作。

一个nil值的slice并没有底层数组。
在底层,make创建了一个匿名的数组变量,然后返回一个slice

4.2.1. append函数

slice并不是一个纯粹的引用类型,它实际上是一个类似下面结构体的聚合类型:

type IntSlice struct {    ptr      *int    len, cap int}
我们通常会这样使用nonempty等操作slice的函数:data = nonempty(data)
4.4 结构体
点操作符也可以和指向结构体的指针一起工作,默认在指针前加*。
如果将EmployeeByID函数的返回值从*Employee指针类型改为Employee值类型,那么更新语句将不能编译通过,因为在赋值语句的左边并不确定是一个变量(译注:调用函数返回的是值,并不是一个可取地址的变量)。
一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身。(该限制同样适应于数组。)但是S类型的结构体可以包含*S指针类型的成员
seen := make(map[string]struct{})
声明变量就已给这个变量分配了相应大小的空间,赋值则是把值拷贝到这个空间里。(tips:这里体现了强类型语言和弱类型语言的区别)
go里的指针可以用来变量引用,但是go不允许指针运算操作。
Go语言有一个特性让我们只声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。
得意于匿名嵌入的特性,我们可以直接访问叶子属性而不需要给出完整的路径
其中匿名成员Circle和Point都有自己的名字——就是命名的类型名字——但是这些名字在点操作符中是可选的。
但结构体字面值必须遵循形状类型声明时的结构

第八章 Goroutines和Channels

除了从主函数退出或者直接退出程序之外,没有其它的编程方法能够让一个goroutine来打断另一个的执行,但是我们之后可以看到,可以通过goroutine之间的通信来让一个goroutine请求其它的goroutine,并让其自己结束执行。
8.3 示例:并发的echo服务
当main goroutine碰到输入终止时,例如,用户在终端中按了Control-D(^D),这时程序就会被终止,尽管其它goroutine中还有进行中的任务。(在8.4.1中引入了channels后我们会明白如何让程序等待两边都结束)。
8.4 channels
和map类似,channel也一个对应make创建的底层数据结构的引用。
Channel还支持close操作,用于关闭channel,随后对基于该channel的任何发送操作都将导致panic异常。对一个已经被close过的channel之行接收操作依然可以接受到之前已经成功发送的数据;如果channel中已经没有数据的话讲产生一个零值的数据。

8.4.2. 串联的Channels(Pipeline)

当一个channel被关闭后,再向该channel发送数据将导致panic异常。当一个被关闭的channel中已经发送的数据都被成功接收后,后续的接收操作将不再阻塞,它们会立即返回一个零值。
没有办法直接测试一个channel是否被关闭,但是接收操作有一个变体形式:它多接收一个结果,多接收的第二个结果是一个布尔值ok
Go语言的range循环可直接在channels上面迭代。
当channel没有被引用时将会被Go语言的垃圾自动回收器回收。但文件一定要手动关。
只有在发送者所在的goroutine才会调用close函数
任何双向channel向单向channel变量的赋值操作都将导致该隐式转换。没有反向转换的语法
如果我们使用了无缓存的channel,那么两个慢的goroutines将会因为没有人接收而被永远卡住。这种情况,称为goroutines泄漏,这将是一个BUG。和垃圾变量不同,泄漏的goroutines并不会被自动回收,因此确保每个不再需要的goroutine能正常退出是重要的。
无缓存channel更强地保证了每个发送操作与相应的同步接收操作;但是对于带缓存channel,这些操作是解耦的。同样,即使我们知道将要发送到一个channel的信息的数量上限,创建一个对应容量大小带缓存channel也是不现实的,因为这要求在执行任何接收操作之前缓存所有已经发送的值。如果未能分配足够的缓冲将导致程序死锁。
8.5. 并发的循环回忆一下之前在5.6.1节中,匿名函数中的循环变量快照问题。上面这个单独的变量f是被所有的匿名函数值所共享,且会被连续的循环迭代所更新的。当新的goroutine开始执行字面函数时,for循环可能已经更新了f并且开始了另一轮的迭代或者(更有可能的)已经结束了整个循环,所以当这些goroutine开始读取f的值时,它们所看到的值已经是slice的最后一个元素了。显式地添加这个参数,我们能够确保使用的f是当go语句执行时的“当前”那个f。


原创粉丝点击