目录
第一章包含了本教程的基本结构,通过十几个程序介绍了用Go语言如何实现类似读写文件、文本格式化、创建图像、网络客户端和服务器通讯等日常。
第二章描述了Go语言程序的基本元素结构、变量、新类型定义、包和文件、以及作用域等概念。第三章讨论了数字、布尔值、字符串和长亮,并演示了如何让显示和处理Unicode字符。第四章描述了符合类型,从简单的数组、字典、切片到动态列表。第五章涵盖了函数,并讨论了错误处理、panic和recover,还有defer语句。
第一章到第五章是基础部分,主流命令式编程语言这部分都类似。个别之处,Go语言有自己特色的语法和风格,但是大多数程序员能很快适应。其余章节是Go语言特有的:方法、接口、并发、包、测试和反射等语言特性。
Go语言的面向对象机制与一般语言不同。它没有类层次结构,甚至可以说没有类;仅仅通过组合(而不是继承)简单的对象来构建复杂的对象。方法不仅可以定义在结构体上, 而且, 可以定义在任何用户自定义的类型上;并且, 具体类型和抽象类型(接口)之间的关系是隐式的,所以很多类型的设计者可能并不知道该类型到底实现了哪些接口。方法在第六章讨论,接口在第七章讨论。
第八章讨论了基于顺序通信进程(CSP)概念的并发编程,使用goroutines和channels处理并发编程。第九章则讨论了传统的基于共享变量的并发编程。
第十章描述了包机制和包的组织结构。这一章还展示了如何有效地利用Go自带的工具,使用单个命令完成编译、测试、基准测试、代码格式化、文档以及其他诸多任务。
第十一章讨论了单元测试,Go语言的工具和标准库中集成了轻量级的测试功能,避免了强大但复杂的测试框架。测试库提供了一些基本构件,必要时可以用来构建复杂的测试构件。
第十二章讨论了反射,一种程序在运行期间审视自己的能力。反射是一个强大的编程工具,不过要谨慎地使用;这一章利用反射机制实现一些重要的Go语言库函数, 展示了反射的强大用法。第十三章解释了底层编程的细节,在必要时,可以使用unsafe包绕过Go语言安全的类型系统。
每一章都有一些练习题,你可以用来测试你对Go的理解,你也可以探讨书中这些例子的扩展和替代。
Go是一门编译型语言,Go语言的工具链将源代码及其依赖转换成计算机的机器指令(静态编译)。Go语言提供的工具都通过一个单独的命令go调用,go命令有一系列的子命令。最简单的一个字命令就是run。这个命令编译一个或多个.go结尾的源文件、库链接文件,并运行最终生成的可执行文件。
编译器会主动把特定符号后的换行符转换为分号, 因此换行符添加的位置会影响Go代码的正确解析(译注:比如行末是标识符、整数、浮点数、虚数、字符或字符串文字、关键字break
、continue
、fallthrough
或return
中的一个、运算符和分隔符++
、--
、)
、]
或}
中的一个)。
通常情况下,输入来自于程序外部:文件、网络连接、其他程序的输出、敲键盘的用户、命令行参数。
程序导入了两个包,用括号把它们括起来写成列表形式, 而没有分开写成独立的import
声明。两种形式都合法,列表形式习惯上用得多。包导入顺序并不重要;gofmt工具格式化时按照字母顺序对包名排序。
符号:=
是短变量声明(short variable declaration)的一部分, 这是定义一个或多个变量并根据它们的初始值为这些变量赋予适当类型的语句。
s := ""
var s string
var s = ""
var s string = ""
自增语句i++
给i
加1;这和i += 1
以及i = i + 1
都是等价的。对应的还有i--
给i
减1。它们是语句,而不像C系的其它语言那样是表达式。所以j = i++
非法,而且++和--都只能放在变量名后面,因此--i
也非法。
Go语言中这种情况的解决方法是用空标识符
(blank identifier),即_
(也就是下划线)。空标识符可用于任何语法需要变量名但程序逻辑不需要的时候, 例如, 在循环里,丢弃不需要的循环索引, 保留元素值。
for循环有两种形式,一种是在某个区间上便利,还有一种是for initialization;condition;post这三个部分每个都是可以省略的。
bufio
包,它使处理输入和输出方便又高效。Scanner
类型是该包最有用的特性之一,它读取输入并将其拆成行或单词;通常是处理行形式的输入最简单的方法,通常是处理行形式的输入最简单的方法。程序使用短变量声明创建bufio.Scanner
类型的变量input
。
input := bufio.NewScanner(os.Stdin)
每次调用input.Scan()
,即读入下一行,并移除行末的换行符;读取的内容可以调用input.Text()
得到。Scan
函数在读到一行时返回true
,不再有输入时返回false。
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 变量的类型
%% 字面上的百分号标志(无操作数)
注意countLines
函数在其声明前被调用。函数和包级别的变量(package-level entities)可以任意顺序声明,并不影响其被调用。(译注:最好还是遵循一定的规范)。
常量声明的值得值必须是一个数字值、字符串或者一个固定的boolean值。小括号是干嘛的?括号可以用来统一定义和导入;const变量赋值不用加var?const也是一种声明。
Go语言中类似if和switch的关键字有25个,还有30多个预定义的名字,比如int和true等,主要对应内建的常量、类型和函数。
var、const、type和func。包一级的各种类型的声明语句的顺序无关紧要。
零值初始化机制可以确保每个声明的变量都有一个良好定义的值,妈妈再也不用担心我忘记赋初始值了。
包级别的变量会在main入口函数执行前完成初始化,局部变量将在声明语句被执行时完成初始化。
只用于函数内部,简短变量声明语句中至少要声明一个新的变量。
变量是内存的别名。Go语言中返回局部变量的地址也是安全的,但每次的地址是不一样的。
有包一级变量和局部变量,这里的局部变量和c语言的生命周期是有不同的,在堆上分配的内存会受到自动垃圾收集器管理。
在赋值之前,赋值语句右边的所有表达式将会先进行求值,然后再统一更新左边对应变量的值。
许多类型都会定义一个String方法,因为使用fmt包的打印方法时,会优先使用该类型的String方法返回的结果打印。
Go语言将数据类型分为四类:基础类型、复合类型、引用类型和接口类型。
Unicode字符rune类型和int32等价的类型,通常表示一个Unicode码点。同样byte和uint8类型等价的类型,用以强调数值是一个原始的数据而不是一个小的整数。还有一个无符号的整数类型uintptr,没有指定具体的bit大小但是足以容纳指针。
IEEE754浮点数国际标准定义,float32的有效bit位只有23位。
字符串的值是不可变的,将[]rune类型转换应用到UTF8编码的字符串,将返回字符串编码的Unicode码点序列。反过来,则返回进行UTF8编码的的字符串。
无类型包括:无类型布尔类型、无类型整数、无类型浮点数、无类型复数、无类型字符和无类型字符串。
无类型整数常量转换为int,它的内存大小是不确定的,但是无类型浮点数和复数常量则转换为内存大小明确的float64和complex128。 如果不知道浮点数类型的内存大小是很难写出正确的数值算法的,因此Go语言不存在整型类似的不确定内存大小的浮点数和复数类型。
Go语言对待数组的方式和其它很多编程语言不同,其它编程语言可能会隐式地将数组作为引用或指针对象传入被调用的函数(和python和c都不一样,而且切片的本质和python相反)。在访问下标时,*寻址运算符允许不写,还有结构体访问成员时也可以不用*。
同时对应方法中接收器实参类型是*T,形参类型是T,编译器也会隐式的解引用。
数组指针:var p *[5]int
指针数组:var p [5]*int
多个slice可以共享底层的数据。
map中的元素并不是一个变量,因此不能对map的元素进行取址操作。向一个nil值的map存入元素将导致一个panic异常。向map存数据必须先创建map。
结构体字面值语法两种形式不能混合使用,且不能企图在外部包中用第一种顺序赋值的技巧来偷偷初始化结构体种未导出的成员。
在函数调用时,Go语言没有默认参数值,也没有任何方法可以通过参数名制定形参。
大部分的编程语言使用固定大小的函数调用栈,常见的大小是64KB或者2MB不等,递归时会出现栈溢出和安全性问题。与此相反,Go语言使用可变栈不会考虑这些问题。
如果一个函数所有的返回值都有显式的变量名,该函数的return语句可以省略操作数,也就是bare return。
匿名函数可以访问完整的词法环境(lexical environment)
当执行到defer语句时,函数和参数表达式得到计算,直到包含该defer语言的函数执行完毕时(也就是return语句更新返回值变量后)或者panic导致的异常结束,defer后的函数才会被执行。
类似于c和c++中的abort。
与调用一个普通函数相比,使用选择器语法来指定方法的接收器。
接口实现了c++的多态
接口内嵌的个各种风格都可以,主要是对应的方法。
接口的规则:表达一个类型属于某一个接口,只要这个类型实现这个接口。
本小节主要讲解了flag.Value的原理,先有一个struct结构体,然后一个函数xxFlag(name string,value Type,usage string) *Type
上述函数调用flag.CommandLine.Var进行注册,当调用flag.Parse的时候调用结构体相应的Set方法,达到了解析命令行参数的目的
可以比较的类型:基本类型和指针
完全不可以比较的类型:切片,映射,函数
接口值或者包含了接口值的聚合类型(数组和结构体):需要看动态类型(动态类型是可以比较的)和动态值。
package sort
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
满足error接口的接口值类型有:*errorString和Errno类型。
检查操作对象x的动态类型是否和断言的类型匹配(x.(T))。有两种可能,第一种T是一个具体类型,检查接口x的动态类型是否和T相同,如果相等,返回接口x的动态值,类型肯定也是T;第二种T是一个接口类型,检查接口x的动态类型是否满足T,如果满足,接口x动态值没有获取到,仍然是一个有相同动态类型和动态值得接口值x,但接口类型是T,对一个接口类型的类型断言改变了类型的表述方式, 改变了可以获取的方法集合( 通常更大);
主函数返回时,所有的goroutine都会被直接打断,程序退出。
make作用与内置的数据结构:切片、哈希表和Channel。
slice := make([]int, 0, 100)
hash := make(map[int]bool, 10)
ch := make(chan int, 5)//5,表示缓存队列大小
与垃圾变量不同,其中channel可以被垃圾自动回收器回收,泄漏的goroutine并不会自动回收。
Add是计数器加一,必须在worker goroutine开始之前调用。要注意匿名函数中的循环变量快照问题。
range和单独访问通道结合用
①、动态栈。
②、go有自己的调度器,m:n调度,go调度器并不是用一个硬件计时器,而是被go语言本身调度的。
③、gomaxprocs就是指n。
④、goroutine没有ID号。