golang实践笔记
Posted patton
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang实践笔记相关的知识,希望对你有一定的参考价值。
概述:Go语言没有沿袭传统面向对象编程中的诸多概念,比如继承、虚函数、构造函数和析构函数、隐藏的this指针等。但Go的语法是在其它语言长期实践后打磨的考虑,只有实际写的时候才会慢慢体会它的便捷。
1 数据类型
18个基本类型:bool, string, rune, byte, int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, float64, complex64, complex128
3个引用类型:slice, map, channel
其它复合类型:(array, struct 属于基本类型), (function, interface属于引用类型)
在 Go 中,任何类型在申明但未初始化时都对应一个零值:布尔类型是 false ,整型是 0 ,字符串是 "" ,而 nil则代表以下类型的零值:pointer, channel, func, interface, map, slice
2 string byte rune
在Go中 string底层是用byte数组存的,并且是不可以改变的。
例如 s:="Go编程"; fmt.Println(len(s)) 输出结果应该是8因为中文字符是用3个字节存的。len(string(rune(\'编\')))的结果是3;
如果想要获得我们想要的情况的话,需要先转换为rune切片再使用内置的len函数: fmt.Println(len([]rune(s))), 结果就是4了。
所以用string存储unicode的话,如果有中文,按下标是访问不到的,因为你只能得到一个byte。 要想访问中文的话,还是要用rune切片,这样就能按下标访问,rune 是int32 的别名,其切片后的每个元素都是一个数字。
3 切片
1) new返回的类似c,是指针;make返回的是引用,但make支持容量和大小的设置;
上图是一个切片的存储方式,x存储的是一个结构体,结构体的ptr指向实际内容。
2) 当我们用append追加元素到切片时,如果容量不够,go就会创建一个新的切片变量,即变量的地址改变了,从这一点看变量类似对内存数据的引用;如果在make初始化切片的时候给出了足够的容量,append操作不会创建新的切片;
slice是引用类型,将一个slice赋值给另一个slice后,改变其中一个的数据,另一个也会一起变化,即二者结构体数据一致。 但是如果其中一个通过append等方式,改变了实际内容长度或容量,则两个slice的ptr指向内容从此就不一致了,对其中一个的修改也就与另一个无关了。
3) go的引用可以理解为一种特殊的指针,但不能解引用处理;引用通过&取址符获取的是引用变量的地址;
当用for i, var := range 方式遍历切片时,var指向的不是切片的每一个元素,而是一个单独的元素类型内存,通过赋值的方式等于遍历的元素。因此var的地址和切片本身没有关系。
与切片对应的是go的数组, 一旦数组被声明了,那么它的数据类型跟长度都不能再被改变。如果你需要更多的元素,那么只能创建一个你想要长度的新的数组,然后把原有数组的元素拷贝过去。go中数组的处理需要注意的是与c/c++的区别,变量名不等于数组开始指针,而且作为函数参数时不会自动退化为指针。
4 make
make只用于map,slice和channel这3种引用类型,并且不返回指针,返回的是一个值引用。eg:
var p *[]int = new([]int);
*p = make([]int, 100, 100)
对于slice来说,变量可以不用make方式申明定义,但这样每次append的话都可能会重新分配空间,而make后不用,相当于更有效率;
对于map和channel来说,申明定义必须借助make。当然也有:=符赋值方式申明定义,但需要注意的是,这种情况是引用方式赋值,区别于非map,channel的其它类型:=赋值。
map的创建也可以借助字面值: dict := map[string]string{"Red": "#da1337", "Orange": "#e95a22"}。 map 没有容量一说,所以也没有任何增长限制。
5 chan
1) 永远是符号<-进行读取或者写入,比如v,ok := <-c是读取,而c <- v是写入。
2) 读取时,如果没有ok,也是可以读取的;如果closed也是能读的,没有赋值而已;如果要知道是否closed得加ok,也就是除非chan永远不关闭,否则读取应该用v,ok := <-c而不是用v := <-c的方式。
3) 不能向closed的chan写入,所以一般写入时需要用一个信号的chan(一般buffer为1),来判断是否写入或者放弃,用select判断是写入成功了,还是正在关闭需要放弃写入。
4) 如果closed后,chan有数据,读取时ok还是true的,直到chan没有数据了才false
下面是一个简单的timer实现,能很好的辅助理解chan的应用场景:
func TimerFunc(timeout chan bool) { time.Sleep(1e9) // sleep one second timeout <- true } func main() { timeout := make(chan bool, 1) TimerFunc(timeout) for { select { case <-timeout: fmt.Println("timeout!") TimerFunc(timeout) } } }
下面是一个判断channel是否写满的例子:
ch := make (chan int, 1) ch <- 1 select { case ch <- 2: default: fmt.Println("channel is full !") }
6 select
1) select 的功能和select, poll, epoll 相似, 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作
2) select最重要的一个作用就是配合chan,高效地进行数据读写。
7 interface,struct (非常非常重要)
对于struct的方法来说,接受者和调用者的关系参照下表:
receiver(接受者) |
调用者 |
说明 |
this T |
var obj T 和 var obj *T 都可以 |
var obj T会把值传递给this var obj *T会把*obj值传递给this 即当receiver为 this T 时,都是值传递 |
this *T |
var obj T 和 var obj *T 都可以 |
var obj T 会把&obj传给this,相当于引用传递 var obj *T 会把obj传给this,相当于引用传递 即当receiver为 this *T 时,都是引用传递。 |
但要注意,实现接口(interface)的方法时,如果receriver为 this T,则interface实际对象必须为值类型,如果receriver为 this *T ,则interface实际对象必须为指针类型。
1) 接口的转换遵循以下规则:普通类型向接口类型的转换是隐式的;接口类型向普通类型转换需要类型断言。
interface类型可以通过 obj.(type)搭配switch方式 判断类型 ,通过 Comma-ok断言 方式获取指定类型的值。
2) 任何实现了接口I的类型都可以赋值给这个接口类型变量。由于 interface{}包含了0个方法,所以任何类型都实现了interface{}接口,这就是为什么可以将任意类型值赋值给interface{}类 型的变量,包括nil。
3) struct 和 interface都可以嵌入,被嵌入的类型的属性和方法都会自动成为母体的属性和方法,类似于继承。golang通过这种类型嵌入的方式,达到继承的效果。
8 变量
- go的变量作用域与c基本一样,即以大括号为分割。但go有:=的申明定义方式,在局部作用域使用这种方式时,会覆盖同名的高层作用域的变量。
9 异常处理
1. go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理
2.defer的思想类似于C++中的析构函数,不过Go语言中“析构”的不是对象,而是函数,defer就是用来添加函数结束时执行的语句。注意这里强调的是添加,而不是指定,因为不同于C++中的析构函数是静态的,Go中的defer是动态的。
3. go的error类型。error主要作为函数返回值,它是一个interface,具体的error类型需要实现一个Error()string方法,该方法会返回当前error对象的错误信息串。
10.defer
关于defer的经典例子,看完这个也就明白了defer的细节和需要注意的问题:
func calc(index string, a, b int) int { ret := a + b fmt.Println(index, a, b, ret) return ret } func main() { a := 1 b := 2 defer calc("1", a, calc("10", a, b)) a = 0 defer calc("2", a, calc("20", a, b)) b = 1 }
输出结果:
10 1 2 3 20 0 2 2 2 0 2 2 1 1 3 4
结论:
1,defer的参数是在编译时就确定的
2,defer调用是逆序的
以上是关于golang实践笔记的主要内容,如果未能解决你的问题,请参考以下文章