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

Gostring底层是用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等方式,改变了实际内容长度或容量,则两个sliceptr指向内容从此就不一致了,对其中一个的修改也就与另一个无关了。

3) go的引用可以理解为一种特殊的指针,但不能解引用处理;引用通过&取址符获取的是引用变量的地址

   当用for i, var := range 方式遍历切片时,var指向的不是切片的每一个元素,而是一个单独的元素类型内存,通过赋值的方式等于遍历的元素。因此var的地址和切片本身没有关系。

   与切片对应的是go的数组, 一旦数组被声明了,那么它的数据类型跟长度都不能再被改变。如果你需要更多的元素,那么只能创建一个你想要长度的新的数组,然后把原有数组的元素拷贝过去。go中数组的处理需要注意的是与c/c++的区别,变量名不等于数组开始指针,而且作为函数参数时不会自动退化为指针。

4 make

make只用于mapslicechannel3种引用类型,并且不返回指针,返回的是一个值引用。eg:

       var p *[]int = new([]int);

       *p = make([]int, 100, 100)

对于slice来说,变量可以不用make方式申明定义,但这样每次append的话都可能会重新分配空间,而make后不用,相当于更有效率;

对于mapchannel来说,申明定义必须借助make。当然也有:=符赋值方式申明定义,但需要注意的是,这种情况是引用方式赋值,区别于非mapchannel的其它类型:=赋值

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) 不能向closedchan写入,所以一般写入时需要用一个信号的chan(一般buffer1),来判断是否写入或者放弃,用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 interfacestruct (非常非常重要)

对于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)的方法时,如果receriverthis T,则interface实际对象必须为值类型,如果receriverthis *T ,则interface实际对象必须为指针类型。

1) 接口的转换遵循以下规则:普通类型向接口类型的转换是隐式的接口类型向普通类型转换需要类型断言。

interface类型可以通过  obj.(type)搭配switch方式 判断类型 ,通过 Comma-ok断言 方式获取指定类型的值。

2) 任何实现了接口I的类型都可以赋值给这个接口类型变量。由于 interface{}包含了0个方法,所以任何类型都实现了interface{}接口,这就是为什么可以将任意类型值赋值给interface{}类 型的变量,包括nil

 

3) struct interface都可以嵌入,被嵌入的类型的属性和方法都会自动成为母体的属性和方法,类似于继承。golang通过这种类型嵌入的方式,达到继承的效果。

8 变量

  1. go的变量作用域与c基本一样,即以大括号为分割。但go:=的申明定义方式,在局部作用域使用这种方式时,会覆盖同名的高层作用域的变量。

9 异常处理

1. go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理    

2.defer的思想类似于C++中的析构函数,不过Go语言中“析构”的不是对象,而是函数,defer就是用来添加函数结束时执行的语句。注意这里强调的是添加,而不是指定,因为不同于C++中的析构函数是静态的,Go中的defer是动态的。

3. goerror类型。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

结论:

1defer的参数是在编译时就确定的

2defer调用逆序的

 

posted @ 2017-08-23 11:54  patton-heart  阅读(336)  评论(0编辑  收藏  举报