go笔记-2

大部分例子和资料来自于这里

关于数组

数组声明时[]放在类型前面,如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为0
a:=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
2
3
4
a := [...]int{1}
b := append(a[0:1], 1)
b[0]++
fmt.Println(a,b)

输出[1] [2 1]
切片的cap是被切片数组起始元素到数组末尾的元素个数(1-0=1),而len在此处是切片的长度(1-0=1)
添加元素后len+1<cap,需要扩容
扩容之后由于内存地址重新分配,值为原先的拷贝而非引用,因此修改值不会反映到原数组上

由于append的长度不需要变化的时候ptr还是原来的值,这时候append是直接通过修改ptr上的元素来实现的

1
2
3
4
a:=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
2
3
4
a := [...]int{1, 2, 3, 4}
b := a[0:1]
c := append(b, 1)
fmt.Println(a, b, c)

输出[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
4
func f2(num int,nums ...float64){
fmt.Println(nums[0],len(nums))
//do something
}

此时传入的nums类型为 []float64,或者说float的slice

f2可以接受多个float64参数,或者一个slice(需要在后面加上...,一个语法糖)

1
2
3
4
f2(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
2
3
4
5
6
a := map[string]int
b := make(map[string]int)
a["abc"] = 1
value,ok := a["def"]//ok==false
delete(a,"abc")
fmt.Println(len(a))

map是引用类型

string 和 []rune

string以十六进制编码,下标对应1个字节,以下标访问时,如果是unicode且占2字节的东西用%c显示的将不是预期的结果
rune是int32的别名,rune数组转换的string,下标对应1个字符(不论编码),用%c即使是2字节的unicode也可以正常显示

1
2
s:="Hello world"
a:=[]rune(s)

遍历字符

1
2
3
4
s:="Hello World"
for index,rune := range s{
//index代表该字符占的字节数,rune代表该字符
}

[]rune和[]byte可以转string

1
2
3
4
5
byteSlice := []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
3
func modify(arr *[3]int){
arr[0] = 1
}

此时arr[0]是(*arr)[0]的缩写

一般更习惯用slice进行数组修改,而非数组指针

1
2
3
func modify(sls []int){
sls[0] = 90
}

调用方式

1
2
a:=[3]int{1,2,3}
modify(a[:])

不支持指针修改,p++之类的操作是不行的

struct

定义和使用

1
2
3
4
type Employee struct {  
firstName, lastName string
age, salary int
}

1
2
emp2 := Employee{"Thomas", "Paul", 29, 800}
var a Employee

匿名struct

1
2
3
4
5
6
7
8
9
emp3 := 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
3
type A struct{
Abc
}

则可以用a.def代替a.Abc.def来访问a中struct Abc中的def变量(下述的’方法’同理)

当成员变量都可相等比较的时候,struct才可以相等比较,当所有成员都相等时认为相等

方法

1
2
3
func (e Employee) displaySalary() {  
fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}

这样可以定义一个Employere的方法,但是这样的方法修改变量不会影响到原变量,传入的e是原来e的拷贝
要影响需要用指针,即func (e *Employee) displaySalary()
对于这种形式的方法,e.dispaleSalary()将是(&e).displaySalary()的简写

接口

定义

1
2
3
type 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
6
func 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
8
switch i.(type){
case string:
//do something
case int:
//do something
default:
//do something
}

接口可以嵌套,可以声明一个接口,里面嵌套另一个接口
未赋值的接口变量为nil

文章目录
  1. 1. 关于数组
  2. 2. 关于slice
  3. 3. 可变参函数
  4. 4. map
  5. 5. string 和 []rune
  6. 6. 指针
  7. 7. struct
  8. 8. 方法
  9. 9. 接口
|