Go语言相关基础
一、Golang的new和make方法
new和make方法是GO语言内建的两个方法,主要用来创建分配类型内存,但二者有些许不同:
变量声明:
var i int var str string
通过var关键字声明变量,然后在程序中使用,我们不指定其默认值时,这些变量的默认值也是其零值,(PS:建议不要使用零值做一些特殊情况的判断,会对结果产生影响)
对于引用类型,默认值为nil,看一下下面一段代码:
import ( "fmt" ) func main() { var i *int *i=10 fmt.Println(*i) }
结果会输出什么?
运行时会报panic,
panic: runtime error: invalid memory address or nil pointer dereference
对于引用类型的变量,我们不光要声明它,还要为它分配内容空间,否则我们的值放在哪里去呢?这就是上面错误提示的原因。PS:对于值类型的声明不需要,因为已默认分配好了。
还是原来的例子,添加 i = new(int)
// The new built-in function allocates memory. The first argument is a type, // not a value, and the value returned is a pointer to a newly // allocated zero value of that type. func new(Type) *Type
它只接受一个参数,这个参数是一个类型,分配好内存后,返回一个指向该类型内存地址的指针。同时请注意它同时把分配的内存置为零,也就是类型的零值。
再看一个例子:
func main() { u:=new(user) u.lock.Lock() u.name = "张三" u.lock.Unlock() fmt.Println(u) } type user struct { lock sync.Mutex name string age int }
示例中的user
类型中的lock
字段我不用初始化,直接可以拿来用,不会有无效内存引用异常,因为它已经被零值了。
这就是new
,它返回的永远是类型的指针,指向分配类型的内存地址。
make
make
也是用于内存分配的,但是和new
不同,它只用于chan
、map
以及切片的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。
PS:因为这三种类型是引用类型,所以必须得初始化,但是不是置为零值,这个和new
是不一样的。
func make(t Type, size ...IntegerType) Type
二者异同:
二者都是内存的分配(堆上),但是make
只用于slice、map以及channel的初始化(非零值);而new
用于类型的内存分配,并且内存置为零。
make
返回的还是这三个引用类型本身;而new
返回的是指向类型的指针。
new这个内置函数其实不常用,new的作用是可以分配内存让我们使用。但现实中,我们直接使用短语句声明和结构体的初始化达到我们的目的,例如:
x := 0 u := User{}
PS:一般工程中的结构体命名建议使用大写,如TimeSlice,变量的声明使用单驼峰的写法,如timeSlice := TimeSlice{}
二、数组和Slice
Go语言中数组是具有固定长度而且拥有零个或者多个相同数据类型元素的序列。数组长度固定,在Go语言中比较少直接使用。Slice长度可增可减,使用场合较多。
区别:
(1)数组在使用的过程中都是值传递,将一个数组赋值给一个新变量或作为方法参数传递时,是将源数组在内存中完全复制了一份,而不是引用源数组在内存中的地址。
(2)每个Slice都是都源数组在内存中的地址的一个引用,源数组可以衍生出多个Slice。满足了内存空间的复用和数组元素的值的一致性的应用需求。
详细看一下
1、数组中每个元素是按照索引来访问的,索引从0到数组长度减1。Go语言内置函数len()可以返回数组中的元素个数
// 初始化 var a [3] int //3个整数型的数组,初始值是3个0 arr:=[5]int{1,2,3,4,5} //长度为5 var array2 = [...]int{6, 7, 8} //不声明长度 q := [...] int {1,2,3} //不声明长度 r := [...] int {99:-1} //长度为100的数组,只有最后一个是-1,其他都是0
2、slice表示一个拥有相同类型元素的可变长度序列。
slice通常被写为[]T,其中元素的类型都是T;它看上去就像没有长度的数组类型。
数组和slice其实是紧密关联的。slice可以看成是一种轻量级的数据结构,可以用来访问数组的部分或者全部元素,而这个数组称之为slice的底层数组。
Slice有三个属性:指针,长度和容量。指针指向数组的第一个可以从slice中访问的元素,这个元素不一定是数组的第一个元素。长度指的是slice中的元素个数,不能超过slice的容量。指针通常是从指向数组的第一个可以从slice中访问的元素,这个元素不一定是数组的第一个元素。长度指的是slice中的元素个数,它不能超过slice的容量。容量的大小通常大于等于长度,会随着元素个数增多而动态变化。Go语言的内置函数len()和 cap()用来返回slice的长度和容量。
//初始化 s1 := []int{1, 2, 3} //注意与数组初始化的区别,在内存中构建一个包括有3个元素的数组, //然后将这个数组的应用赋值给s这个Slice a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} //a是数组 s2 := a[2:8] //从数组中切片构建Slice s3 := make([]int, 10, 20) //make函数初始化,len=10,cap=20
len和cap关系
在添加元素时,若cap容量不足时,cap一般扩容2倍。
注:Slice的扩容规则
// 如果新的大小是当前大小2倍以上,则直接扩容为这个新的cap; // 否则循环以下操作:如果当前大小小于1024,按每次2倍增长,否则每次按当前大小1/4增长。直到增长的大小超过或等于新cap。
三、无缓冲 Channel 和有缓冲 Channel
首先,要了解一个概念:
阻塞:
- 在执行过程中暂停,以等待某个条件的触发 ,我们就称之为阻塞
我们创建Channel有两种方式:
- 缓冲
channel
即buffer channel
创建方式为make(chan TYPE,SIZE)
- 如
make(chan int,3)
就是创建一个int
类型,缓冲大小为3
的channel
- 如
- 非缓冲
channel
即unbuffer channel
创建方式为make(chan TYPE)
- 如
make(chan int)
就是创建一个int
类型的非缓冲channel
- 如
c1:=make(chan int) 无缓冲 c2:=make(chan int,1) 有缓冲
无缓冲的 不仅仅是 向 c1 通道放 1 ,而是一直要有别的协程 <-c1 接手了 这个参数,那么c1<-1才会继续下去,要不然就一直阻塞着
而 c2<-1 则不会阻塞,因为缓冲大小是1 只有当 放第二个值的时候 第一个还没被人拿走,这时候才会阻塞。
无缓冲Channel是同步的,有缓冲Channel是非同步的
// channel 中自带缓冲区。创建时可以指定缓冲区的大小。 // w:直到缓冲区被填满后,写端才会阻塞。 // r:缓冲区被读空,读端才会阻塞。 // len:代表缓冲区中,剩余元素个数, // cap:代表缓冲区的容量。 // 在这里可以举个小小的例子来解释一下有缓冲channel和无缓冲channel // 同步通信: 数据发送端,和数据接收端,必须同时在线。 —— 无缓冲channel // 打电话。打电话只有等对方接收才会通,要不然只能阻塞 // 异步通信:数据发送端,发送完数据,立即返回。数据接收端有可能立即读取,也可能延迟处理。 —— 有缓冲channel 不用等对方接受,只需发送过去就行
// 发信息。短信
缓冲 channel
的阻塞只会发生在 channel
的缓冲使用完的情况下
package main import ( "fmt" "time" ) func loop(ch chan int) { for { select { case i := <-ch: fmt.Println("this value of unbuffer channel", i) } } } func main() { ch := make(chan int,3) ch <- 1 ch <- 2 ch <- 3 ch <- 4 go loop(ch) time.Sleep(1 * time.Millisecond) }
- 这里也会报
fatal error: all goroutines are asleep - deadlock!
,这是因为channel
的大小为3
,而我们要往里面塞4
个数据,所以就会阻塞住 - 解决的办法有两个
- 把
channel
开大一点,这是最简单的方法,也是最暴力的 - 把
channel
的信息发送者ch <- 1
这些代码移动到go loop(ch)
下面 ,让channel
实时消费就不会导致阻塞了
- 把