大部分例子和资料来自于这里
关于数组
数组声明时[]
放在类型前面,如i := [5]int
i := [...]int{1,2,3}
可以自动判断数组大小
数组传参是按值传递的(但slice是按引用的,见下),会创建副本,而非引用原来的数组
可以用len(i)
获取数组长度
可以用for index,value := range(i)
遍历数组
不同长度的数组类型是不同的
关于slice
slice内部有三个变量len
,cap
,ptr
ptr是指向底层数组的指针
cap是ptr的容量
len是slice的长度(含有元素的个数)
没有元素的slice类型是nil,cap和len为0a:=i[1:2]
切片slice,切出来的部分会随着原数组对应值改变而改变
可以用i[:]
代表整体的切片b:=make([]int,0,10)
也可以用来创建slice,此时len(a)
为0,cap(a)
为10
a = append(a,1,2,3,4)
可以对切片添加数据,或b = append(b,a...)
来添加切片a的元素值到b
slice长度变化(扩容)
如果新的大小是当前大小2倍以上,则大小增长为新大小
否则循环以下操作:如果当前大小小于1024,按每次2倍增长,否则每次按当前大小1/4增长。直到增长的大小超过或等于新大小。
1 | a := [...]int{1} |
输出[1] [2 1]
切片的cap是被切片数组起始元素到数组末尾的元素个数(1-0=1),而len在此处是切片的长度(1-0=1)
添加元素后len+1<cap,需要扩容
扩容之后由于内存地址重新分配,值为原先的拷贝而非引用,因此修改值不会反映到原数组上
由于append的长度不需要变化的时候ptr还是原来的值,这时候append是直接通过修改ptr上的元素来实现的1
2
3
4a:=make([]int,3,5)
x:=append(a,1)
y:=append(a,2)
fmt.Println(x,y)
输出[0 0 0 2] [0 0 0 2]
可见x本来应该是1的值因为y的append变成了2
因为a不需要扩容(len(a)+1=3+1<=5=cap(a)),x,y指向的还是a的ptr,append在a的len往后一位修改内存
append不会改变传入数组的属性,a的len不变,因此两次append都是往a的第四个元素修改
如果第3句改为y:=append(x,2)
就不会出现上述的问题,因为x的len是4,会后移一位修改,此时y的输出将会是[0 0 0 1 2]
1 | a := [...]int{1, 2, 3, 4} |
输出[1 1 3 4] [1] [1 1]
也同样反应了上述观点,len=1-0=1
,cap=4-0=4
,len+1<cap
,没有扩容
直接往b的len的下一位修改,而b的ptr指向的是a的第0个元素,所以修改的是a的第1个元素,即a的元素2被改成了1
嗯……总之go的slice使用的时候要注意
可变参函数
在类型前面加上...
(限最后一个参数使用)1
2
3
4func f2(num int,nums ...float64){
fmt.Println(nums[0],len(nums))
//do something
}
此时传入的nums类型为 []float64,或者说float的slice
f2可以接受多个float64参数,或者一个slice(需要在后面加上...
,一个语法糖)1
2
3
4f2(1,1.1,1.2,1.3,1.4)
a:=[...]float64{1.1,2.2,3.3}
f2(1,a[0:3]...)
如果传入了slice,函数内部对slice内元素的修改将会反映到原来的slice上(可以理解为对指针的修改?)
map
1 | a := map[string]int |
map是引用类型
string 和 []rune
string以十六进制编码,下标对应1个字节,以下标访问时,如果是unicode且占2字节的东西用%c显示的将不是预期的结果
rune是int32的别名,rune数组转换的string,下标对应1个字符(不论编码),用%c即使是2字节的unicode也可以正常显示1
2s:="Hello world"
a:=[]rune(s)
遍历字符1
2
3
4s:="Hello World"
for index,rune := range s{
//index代表该字符占的字节数,rune代表该字符
}
[]rune和[]byte可以转string1
2
3
4
5byteSlice := []byte{0x43,0x61,0xC3,0XA9}
str1:=string(byteSlice)
runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072}
str2:=string(runeSlice)
string的utf8编码下字符长度用utf8.RuneCountInString(s)
string是不可修改量,可以通过下标获取,但是不能修改,要修改可以先转为[]rune,改完后再转回string
指针
···
var a *int
//a==nil
b:=1
c:=&b
*c++
···
对数组的指针传参1
2
3func modify(arr *[3]int){
arr[0] = 1
}
此时arr[0]是(*arr)[0]的缩写
一般更习惯用slice进行数组修改,而非数组指针1
2
3func modify(sls []int){
sls[0] = 90
}
调用方式1
2a:=[3]int{1,2,3}
modify(a[:])
不支持指针修改,p++
之类的操作是不行的
struct
定义和使用1
2
3
4type Employee struct {
firstName, lastName string
age, salary int
}
1 | emp2 := Employee{"Thomas", "Paul", 29, 800} |
匿名struct1
2
3
4
5
6
7
8
9emp3 := struct {
firstName, lastName string
age, salary int
}{
firstName: "Andreah",
lastName: "Nikola",
age: 31,
salary: 5000,
}
对于struct指针,可以用a.abc
代替(*a).abc
来访问a
中的属性abc
对于struct嵌套,若未声明变量名(即定义如下时,假设Abc为另一个struct,有def属性)1
2
3type A struct{
Abc
}
则可以用a.def
代替a.Abc.def
来访问a中struct Abc
中的def
变量(下述的’方法’同理)
当成员变量都可相等比较的时候,struct才可以相等比较,当所有成员都相等时认为相等
方法
1 | func (e Employee) displaySalary() { |
这样可以定义一个Employere的方法,但是这样的方法修改变量不会影响到原变量,传入的e是原来e的拷贝
要影响需要用指针,即func (e *Employee) displaySalary()
对于这种形式的方法,e.dispaleSalary()
将是(&e).displaySalary()
的简写
接口
定义1
2
3type A interface{
abc()[]int
}
当一个struct实现了所有接口的方法,它将会自动应用接口,而不需要自己显式地添加什么
实现了接口的struct可以被赋值给接口类型,如果a变量的类型实现了A接口,那么就可以var b A:=a
任何数据都应用了空接口interface{}
(没有任何方法的接口),所以可以用空接口来为属性传入任何类型的参数
可以利用a := i.(int)
来断言传入的参数i类型为int,如果不是则报运行时错误
可声明ok变量,这样就不会报运行时错误,然后根据ok变量的值做进一步的判断1
2
3
4
5
6func assertion(i interface{}){
s,ok:=i.(int)
if ok {//是int类型
fmt.Println(s)
}
}
字面量作为这种函数的参数的时候,将会转化为默认类型,传入参数为字面常量8
时,断言int是true,int8为false
可以在switch中使用断言,断言的类型判断也可以是接口1
2
3
4
5
6
7
8switch i.(type){
case string:
//do something
case int:
//do something
default:
//do something
}
接口可以嵌套,可以声明一个接口,里面嵌套另一个接口
未赋值的接口变量为nil