本文参考:
指针
指针的操作类似cpp
- 指针类型声明
*T
是指向 T 类型值的指针,这和cpp声明有点差别。其零值为nil
。 &
操作符会生成一个指向其操作数的指针。*
操作符解引用得到指向的底层值。
package main
import "fmt"
func main() {
//声明 *int类型的指针e
var e *int //零值nil
fmt.Println(e)
i, j := 42, 1000
p := &i // 取得i的内存地址
fmt.Println(*p) // 解引用读取 i 的值
*p = 21 // 解引用修改 i 的值
fmt.Println(i)
p = &j // p重定向,指向j的地址
*p = *p / 10 // 通过指针对 j 进行除法运算修改值
fmt.Println(j)
}
输出:
<nil>
42
21
100
结构体
- 使用
Name:value
语法可以仅给结构体部分字段赋值 - 结构体字段使用
.
访问 - 结构体指针的访问
(*p).X
简化成隐式间接引用p.X
package main
import "fmt"
type Node struct {
X int
Y int
}
var (
// 创建一个 Node 类型的结构体
v1 = Node{1, 1}
// 使用 Name:语法可以仅给结构体部分字段赋值, X:0 被隐式地赋予
v2 = Node{Y: 1}
//
v3 Node
// 创建一个 *Node 类型的结构体(指针)
p = &Node{1, 2}
)
func main() {
// 类似cpp,声明的空结构体能直接使用.访问
v3.X = 777
v3.Y = 666
fmt.Println(v3)
fmt.Println(v1, v2, p)
// 结构体指针的访问,(*p).X 简化成隐式间接引用p.X
p.X = 1e9
fmt.Println(p)
}
输出:
{777 666}
{1 1} {0 1} &{1 2}
3
&{1000000000 2}
数组
- 类型
[n] T
表示拥有 n 个 T 类型的值的数组,先声明再往里面赋值,用法和cpp类似,不同于用Java要new出空间 - 数组的长度是其类型的一部分,因此数组不能改变大小
package main
import "fmt"
func main() {
//先声明再赋值
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
//声明的同时初始化
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}
Go中数组类型默认是值传递,在方法中修改的话不会影响原来的值。但是可以通过传递指针使其变为引用传递。
// 值传递
func toValueList(val [3]int){
val[0]=4
val[1]=5
val[2]=5
}
func main(){
c := [3]int{1,2,3}
toValueList(c)
fmt.Println(c)
} // [1,2,3]
// 引用传递
func toValueList(val *[3]int){
val[0]=4
val[1]=5
val[2]=6
}
func main(){
c := [3]int{1,2,3}
toValueList(&c)
fmt.Println(c)
} // [4,5,6]
之前在写程序中碰到的一个疑惑点可以拿出来,和下面的切片有关:
// 定义了一个切片的数组
d := [3][]string{{"abc", "def"}, {"111", "222"}}
q := d[0] // 传递了数组的一个元素为切片
d[0] = nil
fmt.Println(q) //[abc def] d[0]为nil并没有影响传递的数组元素
fmt.Println(d) // [[] [111 222] []]
//如果像下面这样
q := d[0]
d[0][1] = "x" // 改变了切片里的一个元素
fmt.Println(q) // [abc x] // 改变了底层切片的元素
fmt.Println(d) // [[abc x] [111 222] []]
切片
- 切片类似于没有长度的数组,可以动态伸缩改变,类型
[]T
表示一个元素类型为 T 的切片。 - 切片的操作类似于Python的切片操作。
- 切片像是数组的引用,不存储数据,只是描述了底层的一段。这点需要重点理解,是精髓。
– 如果更改切片元素,会通过引用将底层的也改变,与它共享底层数组的切片都会观测到这些修改。
– 在后面创建了固定容量的切片以后,我们通过切片操作进行长度截断,但是底层数组还在,可以在原先截断的基础上用“超越其长度”的切片进行扩容(理论上不能超过原容量)。
package main
import (
"fmt"
)
func main() {
//切片类似于没有长度的数组,类型 []T 表示一个元素类型为 T 的切片。
q := []int{2, 3, 5, 7, 11}
fmt.Println(q)
r := []bool{true, false, true, true, false, true}
fmt.Println(r)
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}
fmt.Println(s)
// 可以创建二维切片
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
fmt.Println(board)
// 可以从数组中创建切片
primes := [6]int{1, 2, 3, 4, 5, 6}
// 切片操作,下界默认为0,上界默认为切片长度,类似于python切片
var a = primes[1:4]
fmt.Println(a) //[2,3,4]
var b = primes[2:]
fmt.Println(b) //[3,4,5,6]
a = a[:2]
fmt.Println(a) //[2,3]
a = a[1:]
fmt.Println(a) //[3]
// 切片像是数组的引用,不存储数据,只是描述了底层的一段
// 如果更改切片元素,会通过引用将底层的也改变,与它共享底层数组的切片都会观测到这些修改
names := []string{"jxz1", "jxz2", "jxz3", "jxz4"}
var k1 = names[0:2]
var k2 = names[1:3]
fmt.Println(k1, k2) // [jxz1 jxz2] [jxz2 jxz3]
// 修改切片元素k2
k2[0] = "XXX"
// 其他切片和底层都会被改变
fmt.Println(k1, k2) // [jxz1 XXX] [XXX jxz3]
fmt.Println(names) // [jxz1 XXX jxz3 jxz4]
}
切片的长度是它所包含的元素个数,可以通过len(s)获取
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
容量的改变重点在于第一个元素的位置是否发生变化,否则长度变化仍然不影响容量。
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// 截取切片使其长度为 0
s = s[:0]
printSlice(s)
// 拓展其长度,底层的容量够,是允许它改变长度,引用到原来的s的
s = s[:4]
printSlice(s)
// 舍弃前两个值
s = s[2:]
printSlice(s)
// 切片零值为nil
var p []int
fmt.Println(p, len(p), cap(p))
if p == nil {
fmt.Println("nil!")
}
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
输出:
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 [] //长度改变,容量不变
len=4 cap=6 [2 3 5 7] // 长度改变,容量不变
len=2 cap=4 [5 7] //长度改变,第一个元素改变,容量改变
[] 0 0 //切片零值
nil!
用make创建切片:
package main
import "fmt"
func main() {
// 创建容量为5,长度为5的切片,元素为零值
a := make([]int, 5)
printSlice("a", a)
// 创建容量为0,长度为5的切片,元素为零值
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c)
d := c[2:5]
printSlice("d", d)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
输出:
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]
向切片中添加元素
package main
import "fmt"
func main() {
// 创建空切片
var s []int
printSlice(s)
// 添加0
s = append(s, 0)
printSlice(s)
// 添加1
s = append(s, 1)
printSlice(s)
// 可以一次性添加多个元素,容量更大
s = append(s, 2, 3, 4)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
输出:
len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=5 cap=6 [0 1 2 3 4]
range
for循环的range形式可遍历切片或者映射,可返回索引和元素值。
package main
import "fmt"
func main() {
var s = []int{11, 12, 13, 14, 15, 16, 17, 18}
//遍历切片
//只遍历索引,索引从0开始
for i := range s {
fmt.Print(i, " ")
}
fmt.Println()
//遍历索引和值,索引从0开始
for j, k := range s {
fmt.Printf("%d %d \n", j, k)
}
//只遍历值
for _, v := range s {
fmt.Print(v, " ")
}
fmt.Println()
// 遍历map
mapt := make(map[int32]string) // New empty set
mapt[1] = "a" // Add
mapt[2] = "b"
mapt[3] = "c"
mapt[4] = "d"
delete(mapt, 2)
// 输出key,value
for k, v := range mapt {
fmt.Println("k:", k, "v:", v)
}
// 只输出key
for k := range mapt { // Loop
fmt.Println(k)
}
}
输出:
0 1 2 3 4 5 6 7 //只输出索引
0 11 // 输出索引和值
1 12
2 13
3 14
4 15
5 16
6 17
7 18
11 12 13 14 15 16 17 18 //只输出值
k: 1 v: a // 输出key,value
k: 3 v: c
k: 4 v: d
1 //只输出key
3
4
映射
映射的类型 map[key]value
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
//定义string-Vertex的映射切片
var m = map[string]Vertex{
"jxz1": {1, 1},
"jxz2": {2, 2},
}
func main() {
fmt.Println(m)
// 定义string-int的映射
m := make(map[string]int, 10)
//插入元素
m["a"] = 1
fmt.Println("The value:", m["a"]) //1
//修改元素
m["a"] = 2
fmt.Println("The value:", m["a"]) //2
//删除元素
delete(m, "a")
fmt.Println("The value:", m["a"]) // 0 键不存在,返回零值
//通过双赋值检测键是否存在, v返回0值,ok=false
v, ok := m["a"]
fmt.Println("The value:", v, "Exist?", ok) // 0 false
}