《Go语言编程》阅读笔记(1) 基本的语法和语言特点
初识Go
- 静态语言 
- 语言特性: - 自动垃圾回收 
- 内置类型丰富 
- 多返回值的函数 
- 错误处理:关键字defer、panic、recover 
- 匿名函数、闭包——函数可作为参数传递 - 1 
 2
 3- f := func(x, y int) int { 
 return x+y
 }
- 并发编程:每个用关键字go执行的函数可以运行成为一个单位协程,一个协程阻塞,调度器把其他协程安排到另外的线程执行;用通信顺序进程CSP作为goroutine间的通信方式,用channel实现CSP(类似管道pipe) 
- 类和接口 
- 反射:获取对象类型的详细信息,需要import reflect 
 
- 通过Cgo重用现有的C模块,利用Cgo的特定语法混合C代码 
- GOPATH和PATH环境变量类似,能接受多个路径 
- 源码开头为package声明,表明该源码所在的包,包是基本的分发单位;生成Go的exe,必须建立名为main的包,其中包含main函数,main()无参数,无返回值,命令行参数保存在os.Args,flag包用于规范命令行参数并获取、解析命令行参数 
- 函数:返回值没有明确赋值时将被设置为默认值 - 1 
 2
 3- func 函数名(参数列表)(返回值列表){ 
 //参数1 参数1数据类型, 参数2 参数2数据类型, ...
 }
- 注释格式同C++ 
- 左边花括号不允许另起一行 
- 问题追踪: - fmt包提供打印函数,其中Printf与PrintIn()类似于C的Printf()   
- log包:日志功能 
- go内置了print函数,但属于输出到标准错误流中并打印 
 
顺序编程
变量
- 变量声明:关键字var,变量类型在变量名后;若干声明的变量可以放置在一起   
| 1 | var ( | 
- 变量初始化:可以删去var, - var v1 int = 10和- var v2 = 10和- v3 := 10是相同的,符号:=表示变量声明和初始化同时进行,此时左边的变量不能是被声明过的
- go的变量声明后必须使用 
- 可以有 - i,j=j,i
- 函数可以有多个返回:   
常量
- 字面常量:无类型,只要在相应类型的值域里就可以作为该类型的常量,如-12可以赋值int、uint、int32、float64、complex64等类型的变量 
- 可以通过const关键字定义,可以不限定常量类型   
- 三个预定义常量:true、false、iota;iota在每一个关键字出现时被重置为0,下一个const出现前每出现一次iota,其对应数字自动加一   
- 枚举一系列的常量 - 1 
 2
 3
 4- const ( 
 Friday = iota
 number_of_days
 )
- 大写字母的开头的常量在包外可见 
数据类型
- bool: - var v1 := (1==2),不接受其他类型赋值,不支持自动和强制类型转换
- 整型:int和int32是不同类型,需要强制转换 - value2 = int32(value1)(存在精度损失、值溢出的问题) 
- 支持+-*/和%运算,支持比较运算(同C),不同类型的整型数不能直接比较,会编译报错,但可以直接和字面常量比较 - 1 
 2
 3
 4
 5- var i int32 = 1 
 var j int64 = 2
 if i == 1 || j == 2 {
 ...
 }
- 位运算和C类似,取反为 - ^x
- float64相当于double, - value1 := 12.0,浮点数的比较建议使用:- 1 
 2
 3
 4
 5- import "math" 
 // p 为自定义的比较精度,如0.0001
 func IsEqual(f1, f2, p float64){
 return math.Fdim(f1, f2) < p
 }
- 复数: - var value1 complex64 = 3.2 + 12i——两个float32构成,通过- real()和- imag()获得实部和虚部
- 字符串: - var a string = "abc",通过数组下标获取内容,不能在初始化后修改,通过- len()获取长度,字符串连接用运算符+
- 支持两个字符类型:byte和rune(代表单个Unicode字符),go的多数api假设字符串为utf-8编码 
- 数组声明方法:  * 遍历:`for i:=0; i< len(array); i++ {}`或`for i, v := range array {}`——range:索引+元素值,类似enumerate * 遍历:`for i:=0; i< len(array); i++ {}`或`for i, v := range array {}`——range:索引+元素值,类似enumerate- go中数组为值类型(value type),所有值类型变量赋值和参数传递时产生一次复制——数组作为函数参数时,会复制一次,函数体无法修改外面数组内容 
- 修改方法:数组切片,数组切片分为三个变量,一个指向原生数组的指针,数组切片中元素个数,数组切片已分配的存储空间;数组切片仍使用数组管理元素(类似C++中vector和数组的关系) - 根据一个数组创建数组切片:   
- 根据 - make()创建数组切片:(make只能为slice, map, channel分配内存)  
- 操作数组的所有方法都适用于数组切片 
- cap()返回数组切片分配空间大小,- len()返回数组切片当前元素数目
- append():- slice = append(slice, 1, 2, 3),尾部加上3个元素,生成一个新的数组切片;也可- slice = append(slice, slice2...)(slice2后面有3个点,不能省略)
- 基于数组切片创建数组切片,只要选择范围不超过 - cap(oldslice)即可,多余位置补0- 1 
 2- oldslice := []int {1,2,3} 
 newslice := oldslice[:5]
- copy(slice1, slice2):按较短的数组切片元素个数进行复制
 
 
- map:类似dict,键值对的未排序组合   - 其中的string为键的类型,后面PersonInfo为值类型
- 通过make创建一个新的map,可以创建时指定其初始存储能力:cur_map := make(map[string] int, 100),或者创建并初始化一个map:tmp_map = map[string] int{"abc": 1}
- 通过delete(cur_map, key1)删除键为key1的键值对,若不存在也不报错
 
流程控制
- 条件语句:类似C,不允许将最终的return语句包含在if else结构中,花括号必须存在 
- 选择语句:不需要break明确跳出一个case,只有在case中添加fallthrough关键字,才会执行下一个case;若不设定switch后的条件表达式,则整个switch类似于多个if else;     
- 循环语句只有for,用下面的写法替代while;不支持以逗号间隔的多个赋值语句;break、continue类似C,但break可以选择中断哪个循环     
- goto:跳转到本函数内的某个标签   
函数
- 若参数列表中若干个相邻参数的类型相同,则参数列表中额可以省略前面变量的类型声明 - 1 
 2
 3
 4
 5
 6- func Add(a, b int)(ret int, err error) { 
 ...
 }
 func Add(a, b int) int {
 //如果只有一个返回值
 }
- 调用:如果导入过该函数所在的包,则: - c := math.Add(1, 2)
- Go中函数名字大小写体现了该函数的可见性,有时导入了包,但编译器仍会报错无法找到 - add_xxx函数——大写字母开头的函数才能被其他包使用,此规则同样适用于类和变量的可见性
- 不定参数:函数传入的参数数目不确定(下例中,参数类型都是int),形如 - ...type格式的类型只能作为参数类型且为最后一个参数,本质上是一个数组切片。如果希望传递任意类型,则为- ...interface{}- 1 
 2
 3
 4
 5
 6
 7
 8- func tmp1(args ...int) { 
 for _, arg := range args {
 fmt.Println(arg)
 }
 }
 func tmp2(format string, args ...interface{}) {
 //...
 }
- 可以给返回值命名,其值在函数开始时自动初始化为空: - func tmp()(n int, err Error){}
- 匿名函数:不定义函数名,可以直接赋值给一个变量或直接执行;go的匿名函数是一个闭包 - 1 
 2
 3
 4
 5
 6
 7- f := func (a, b int, z float64) bool { 
 return a*b < int(z)
 }
 fmt.Println(f(1, 2, 3.5))
 func (ch chan int) {
 ch <- ACK
 }(reply_chan) // 花括号直接跟参数列表,表示函数调用- 错误处理- error接口: - 接口定义为:   
- 对于函数,如果要返回error,error通常作为多种返回值的最后一个   
- 举例: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17- type PathError struct { 
 Op string
 Path string
 Err error
 }
 func (e *PathError) Error() string{
 return e.Op + " " + e.Path + ": " + e.Err.Error()
 }
 func Stat(name string) (fi FileInfo, err error){
 var stat syscall.Stat_t
 err = syscall.Stat(name, &stat)
 if err != nil{
 return nil, &PathError("stat", name, err)
 }
 
 return fileInfoFromStat(&stat, name), nil
 }
 
- defer:例子如下,一个函数可以存在多个defer语句,defer的调用服从先进后出   
- panic()和recover():报告和处理运行时的错误:   - panic()调用时,正常执行流程停止,函数中defer关键字延迟执行的语句开始执行,函数返回到调用它的位置,并逐层向上执行panic流程,直到所属的goroutine中所有在执行的函数终止。报告错误信息,包括调用panic()时传入的参数——此过程称为错误处理流程 
- recover()用于终止错误处理流程,一般应该在一个使用了defer的函数中执行   
 
 
实例
- 结构如下,sorter.go:package main, - import "algorithm/bubblesort",- import "algorithm/qsort";bubblesort.go:package bubblesort;bubblesort_test.go:单元测试文件,package bubblesort;qsort.go和qsort_test.go类似  
- flag包解析命令行参数(一些变量可以定义在函数外,作为全局变量,这里*string为指针,类似C++,此时如果要在另一个函数内部修改数组,直接传递指针或数组切片即可)   
- 文件读取操作,需要导入os包打开文件, - file, err = os.Open(infile),文件关闭需要用- defer file.close(),然后通过bufio包读取文件内容
- 文件输出操作,先生成输出文件 - file, _, _ = os.Create(outfile),之后- file.WriteString(string1)
- strconv包实现基本数据类型与字符串表示的转换, - strconv.Itoa(x int) string:接收一个int类型参数,返回x的字符串(反之为- Atoi)
- 单元测试文件一般需要导入testing包,一个样例如下   
面向对象
类型系统
- 可以给任意类型(包括内置类型)添加新的函数,以int为例,定义一个新的类型Integer(就是int)并增加一个新的函数Less(),此时若 - var a Integer = 1有- a.Less(2)    
- Go中没有类似python的self或类似C++的this;上例中,如果要修改对象,可以改用指针:   
- http包中关于HTTP头部信息的Header类型和上例也类似:   
- 值语义和引用语义: - 对于b=a,如果b的修改不影响a的值,则此类型为值类型。go中大多数类型为值类型,包括:基本类型、数组array(这和C++完全不同)、结构体struct、指针等,若要表达引用,必须:var b = &a
- 有4个类型具有引用含义:数组切片(指向数组的一个区间)、map、channel、interface
 
- 对于
- 结构体:和其他语言的class具有同等地位,但放弃了很多面向对象特性,只保留了组合特性。例如,定义一个矩形类型,并定义一个成员方法计算矩形的面积(Rect为类的名称,该类是一个struct) - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10- type Rect struct { 
 x, y float64
 width, height float64
 }
 func (r *Rect) Area() float64 {
 return r.width * r.height
 }
 ...
 tmp := &Rect{x: 1, y: 2, width: 2, height: 2}
 fmt.Println(tmp.Area())
初始化
- 初始化方式如下,没有显式初始化都会被初始化为该类型的零值   
- go没有构造函数,对象的创建通常由一个全局的创建函数完成(以NewXXX命名):   
匿名组合
- 以匿名组合的方式实现继承,如下例,定义Base类,有两个成员函数Foo()和Bar(),定义一个Foo类,继承并改写了Bar()方法 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12- type Base struct { 
 Name string
 }
 func (base *Base) Foo() {...}
 func (base *Base) Bar() {...}
 type Foo struct {
 Base
 }
 func (foo *Foo) Bar() {
 foo.Base.Bar()
 ...
 }
- 派生类Foo没有改写即类的成员方法时,相应的方法就被继承,例如foo.Foo()和foo.Base.Foo()效果相同 
- 可以以指针的方式得到“派生类”,此时Foo创建实例时,需要提供一个Base实例的指针——类似于C++的虚基类 - 1 
 2
 3- type Foo struct { 
 *Base
 }
可见性
- 没有private、public、protected这些关键字,若某个符号对其他package可见,则该符号以大写字母开头,成员函数的可访问性类似
- Go语言符号的可访问性是包一级,即包内部的其他类都可以访问它
接口(很重要)
- go之前,其他语言的接口在实现类时,需要明确声明自己实现了某个接口,即强制性接口继承,称为侵入式接口 
- go中,一个类只要实现了接口要求的所有函数,则称这个类实现了该接口,例如定义一个File类,实现了Read、Write、Seek、Close函数,并定义IFile、IReader、IWriter、ICloser接口。     - File没有从这些接口继承,但File实现了这些接口,因此可以进行赋值   
- 此时不再需要绘制类的继承图,并且实现类的时候只需要关心类提供了什么方法,不需要考虑接口要拆分多细才合理,还不用为了实现一个接口而导入一个包 
 
- 接口赋值:包括将对象实例赋值给接口,或者将一个接口赋值为另一个接口 - 前者,要求对象实例实现了接口要求的所有方法,例如,定义一个Integer类型,定义接口LessAdder: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13- type Integer int 
 func (a Integer) Less(b Integer) bool {
 return a < b
 }
 func (a *Integer) Add(b Integer) {
 *a += b
 }
 type LessAdder interface {
 Less(b Integer) bool
 Add(b Integer)
 }
 var a Integer = 1
 var b LessAdder = &a- 此时*Integer既存在Less函数,又存在Add()函数,满足LessAdder接口——Go会根据func (a Integer) Less(b Integer) bool自动生成新的方法func (a *Integer) Less(b Integer) bool {return (*a).Less(b)}
 
- 此时*Integer既存在Less函数,又存在Add()函数,满足LessAdder接口——Go会根据
- 后者,只要两个接口具有相同的方法列表,它们就是等同的,如果接口A的方法列表是接口B的方法列表的子集,则接口B可以赋值给接口A,反之不行 
 
- 接口查询:查询Writer接口能否转化为IStream接口;进一步可以查询接口只想的对象是否为某个类型(file1接口指向的对象实例是否为*File类型——前文里,接口的赋值使用了&) - 1 
 2
 3
 4
 5
 6
 7- var file1 Writer = ... 
 if file5, ok := file1.(IStream); ok {
 ...
 }
 if file6, ok := file1.(*File); ok {
 ...
 }
- 类型查询:查看接口指向对象实例的类型,例如:     
- 接口组合:   
- Any类型:任何对象实例都满足空接口interface{},因此当函数可以接收任意的对象实例时,通常会声明为interface{}( - func Println(args ...interface{}))
实例
- go标准库中没有GUI相关的功能,也没有成熟的第三方界面库——go初始定位为高并发的服务器端程序
- 略
 
        