指针
-
Go
语言的指针部分和C/C++
大致相同,如果有过学习C/C++
指针部分,那你已经明白了大多数情况下指针的用法,这里只指出Go
语言指针与其它语言不同的地方 -
Go
语言中指针无法进行位移和运算,它的作用从C/C++
中的无所不能,在Go
中更像是变为了存储变量地址的变量。虽然如此,它的作用也非常重要。因为在值传递中,它可以减少内存的损耗(地址的内存损耗十分之少) -
指针的一般使用如下所示
package main
import "fmt"
func main() {
str := "Hello World !"
var strP *string
strP = &str
*strP = "Hello Codey !"
fmt.Println("指针strP的地址为", strP)
fmt.Println("指针strP指向的值为", *strP)
fmt.Println("指针str的地址为", &str)
fmt.Println("指针str的值为", str)
}
- 运行结果
$ go run hello.go
指针strP的地址为 0xc00005c270
指针strP指向的值为 Hello Codey !
指针str的地址为 0xc00005c270
指针str的值为 Hello Codey !
数组
数组的声明和初始化
- 数组的一般声明形式诸如
var arrayName [size]dataType
// var 数组名字 [数组大小]数据类型
- 还记得前面的章节中,我们有说过不同数据类型变量的初始值吗?在数组初始化时,他们都会拥有自己的默认值
package main
import "fmt"
func main() {
var number [10]int
for i:=0; i< 10; i++ {
fmt.Println("i = ",i,"number = ",number[i])
}
}
- 这里我们只对
number
进行了声明而并未赋值,最后的输出结果也证明,数组中的元素在声明时就已经有了自己的默认值
$ go run hello.go
i = 0 number = 0
i = 1 number = 0
i = 2 number = 0
i = 3 number = 0
i = 4 number = 0
i = 5 number = 0
i = 6 number = 0
i = 7 number = 0
i = 8 number = 0
i = 9 number = 0
- 当然我们也可以像其它语言那样使用类似集合的形式来声明数组
var numbers = [5]int{1, 2, 3, 4, 5}
numbers := [5]int{1, 2, 3, 4, 5}
- 当我们不知道数组具体大小时,我们也可以使用
...
代替数组的长度,由编译器根据元素个数自主推断数组的长度
var a = [...]int{5,6,7,8,9,10}
a := [...]int{777,222,666,777,888}
- 但如果我们需要在初始化的时候指定某个元素的值,
Go
语言也为我们提供了一种简便的方式
var number = [...]int{1:5,6:8,9:666}
balance := [8]float{0:9.99,5:6.66}
- 我们只需要用诸如
数组索引:指定元素值
的方式就可以指定元素的赋值。如果我们声明的是一个不定数组,最后数组的长度会是我们指定的数组索引+1的。但如果我们已经指定了数组的长度,数组索引就无法大于等于数组长度了,否则会报越界异常
数组的比较
-
Go
语言中数组的比较只会涉及两个部分-
数组的长度是否相等
-
数组中对应值是否完全相同
-
-
当然数组能够比较的基础是二者是相同类型。需要注意的是,
Go
语言中数组的大小也是类型的一部分,不同大小的数组并不兼容,[5]int
和[10]int
是不相同的类型,所以无法比较(在Goland
中IDE
会直接提示类型不匹配) -
下面的例子展示了两个数组的比较方式
package main
import "fmt"
func main() {
var a [2]int
var c = [2]int{1, 2}
var d = [...]int{1, 2}
fmt.Println("a == c ? ", a == c)
fmt.Println("c == d ?", c == d)
}
- 运行结果
$ go run hello.go
a == c ? false
c == d ? true
切片(Slice)
- 切片是对数组的抽象。在特定场景中,数组这样不可变长度的性质就变得不太实用。为此,
Go
语言提供了一种使用灵活,功能更为强大的类型——切片(可以理解为动态数组)
切片的声明和初始化
- 切片的声明形式和数组类似,更像是声明了一个没有长度的数组
var slice_name []type
// var 切片名 []切片类型
-
与数组不同的是,数组中的元素在声明时就有了自己的默认值,而切片没有零值。我们如果想要使用一个切片,必须要在使用前进行初始化。切片声明后它的值就是
nil
(空指针),这是因为它的底层实现就是一个指向数组的指针,在给它存入数组的地址之前,它的值只能是nil
-
我们可以使用
make()
函数对切片进行初始化
package main
import (
"fmt"
)
func main() {
var a []int
fmt.Println("初始化前:", a)
a = make([]int, 5, 10)
fmt.Println("初始化后:", a)
a[4] = 5
fmt.Println(" 赋值后:", a)
a[5] = 6
fmt.Println("赋值后:", a)
}
- 运行结果如下所示
$ go run hello.go
初始化前: []
初始化后: [0 0 0 0 0]
赋值后: [0 0 0 0 5]
panic: runtime error: index out of range [5] with length 5
goroutine 1 [running]:
main.main()
D:/Study/notes/Go/hello.go:14 +0x192
exit status 2
- 对于一个
make
函数,它的参数如下所示
make(切片类型,切片长度,切片容量)
-
切片长度:切片中元素的数量。当切片长度被指定后,相应长度的元素都会拥有零值。在例子中我们可以看到,我们指定
a
这个切片的长度为5
,相应就有5
个元素拥有零值 -
切片容量:切片引用数组的长度。切片的容量一般都会大于等于长度,容量也会随着长度的增长而增长(动态的来源)。我们在初始化一个切片的时候其实就是给切片引用了一个数组,容量就是这个数组的长度,如果切片长度将要超过切片的容量,它就会让切片引用一个更大容量的数组来存放这些元素(有点像
C++
的STL
中vector
的实现方式,先创建一个小容量数组,其后通过倍增,迁移数据) -
我们可以通过一个小实验观察容量的变化过程
package main
import (
"fmt"
)
func main() {
var a = []int{1, 2, 3, 4, 5}
fmt.Printf("a的地址%p,a的长度%d,a的容量%d\n", a, len(a), cap(a))
a = append(a, 6)
fmt.Printf("a的地址%p,a的长度%d,a的容量%d\n", a, len(a), cap(a))
a = append(a, 7, 8)
fmt.Printf("a的地址%p,a的长度%d,a的容量%d\n", a, len(a), cap(a))
b := []int{9, 10, 11}
a = append(a, b...)
fmt.Printf("a的地址%p,a的长度%d,a的容量%d\n", a, len(a), cap(a))
}
- 运行结果如下所示
$ go run hello.go
a的地址0xc000012300,a的长度5,a的容量5
a的地址0xc00001a1e0,a的长度6,a的容量10
a的地址0xc00001a1e0,a的长度8,a的容量10
a的地址0xc0000220a0,a的长度11,a的容量20
- 结果验证了我们的猜想。其中
len()
函数可以让我们获取切片中元素的数量(切片长度),cap()
函数可以让我们获取引用的数组长度(切片容量)
切片的追加
-
上一节我们使用了一个从未见过的函数
append()
,实际上它就是本节要讲述的内容。切片是一个动态的数组,意味着我们可以像C++
的vector
那样在尾部对它追加元素 -
一般的使用形式诸如
append(切片,待添加值)
package main
import (
"fmt"
)
func main() {
var a = []int{1, 2, 3, 4, 5}
a = append(a, 6)
fmt.Println(a)
a = append(a, 7, 8)
fmt.Println(a)
b := []int{9, 10}
a = append(a, b...)
fmt.Println(a)
}
- 运行结果
$ go run hello.go
[1 2 3 4 5 6]
[1 2 3 4 5 6 7 8]
[1 2 3 4 5 6 7 8 9 10]
- 如例所示,如果我们待添加的元素是一个数组或切片,我们可以使用
...
使其一次性添加到切片的末尾
切片的截取
- 如果你学过
python
,这段对你来说一定不会陌生。 我们可以使用诸如切片名[begin:end:max]
的形式创建一个新的切片,其中,截取的片段是一个左闭右开区间[begin,end)
,max
是新切片的容量
package main
import (
"fmt"
)
func main() {
var a = []int{1, 2, 3, 4, 5}
fmt.Println("a[1:3]=", a[1:3])
//截取元素a[1]-a[2],创建一个容量和长度都为2的切片
fmt.Println("a[1:]=", a[1:])
//截取元素a[1]-末尾,创建一个容量和长度都为4的切片
fmt.Println("a[:3]=", a[:3])
//截取元素开头-a[2],创建一个容量和长度都为3的切片
}