复合数据类型
复合数据类型
数组
什么是数组
Go 语言提供了数组类型的数据结构。
数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。
数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。数组的下标取值范围是从 0 开始,到长度减 1。
数组一旦定义后,大小不能更改。
数组的语法
声明和初始化数组
需要指明数组的大小和存储的数据类型。
var variable_name [SIZE] variable_type示例代码:
var balance [10] float32
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}初始化数组中 {} 中的元素个数不能大于 [] 中的数字。
如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}balance[4] = 50.0数组的其他创建方式:
var a [4]float32 // 等价于:var arr2 = [4]float32{}
fmt.Println(a) // [0 0 0 0]
var b = [5]string{"ruby", "王二狗", "rose"}
fmt.Println(b) // [ruby 王二狗 rose ]
var c = [5]int{'A', 'B', 'C', 'D', 'E'} // byte
fmt.Println(c) // [65 66 67 68 69]
d := [...]int{1, 2, 3, 4, 5} // 根据元素的个数,设置数组的大小
fmt.Println(d) // [1 2 3 4 5]
e := [5]int{4: 100} // [0 0 0 0 100]
fmt.Println(e)
f := [...]int{0: 1, 4: 1, 9: 1} // [1 0 0 0 1 0 0 0 0 1]
fmt.Println(f)访问数组元素
float32 salary = balance[9]示例代码:
package main
import "fmt"
func main() {
var n [10]int /* n 是一个长度为 10 的数组 */
var i, j int
/* 为数组 n 初始化元素 */
for i = 0; i < 10; i++ {
n[i] = i + 100 /* 设置元素为 i + 100 */
}
/* 输出每个数组元素的值 */
for j = 0; j < 10; j++ {
fmt.Printf("Element[%d] = %d\n", j, n[j])
}
}运行结果:
Element[0] = 100
Element[1] = 101
Element[2] = 102
Element[3] = 103
Element[4] = 104
Element[5] = 105
Element[6] = 106
Element[7] = 107
Element[8] = 108
Element[9] = 109数组的长度
通过将数组作为参数传递给 len 函数,可以获得数组的长度。
示例代码:
package main
import "fmt"
func main() {
a := [...]float64{67.7, 89.8, 21, 78}
fmt.Println("length of a is", len(a))
}运行结果:
length of a is 4您甚至可以忽略声明中数组的长度并将其替换为 ... 让编译器为你找到长度:
package main
import (
"fmt"
)
func main() {
a := [...]int{12, 78, 50} // ... makes the compiler determine the length
fmt.Println(a)
}遍历数组
package main
import "fmt"
func main() {
a := [...]float64{67.7, 89.8, 21, 78}
for i := 0; i < len(a); i++ { // looping from 0 to the length of the array
fmt.Printf("%d th element of a is %.2f\n", i, a[i])
}
}使用 range 遍历数组:
package main
import "fmt"
func main() {
a := [...]float64{67.7, 89.8, 21, 78}
sum := float64(0)
for i, v := range a { // range returns both the index and value
fmt.Printf("%d the element of a is %.2f\n", i, v)
sum += v
}
fmt.Println("\nsum of all elements of a", sum)
}如果您只需要值并希望忽略索引,那么可以通过使用 _ 空白标识符替换索引来实现这一点:
for _, v := range a { // ignores index
}多维数组
Go 语言支持多维数组,以下为常用的多维数组声明语法方式:
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_typevar threedim [5][10][4]int三维数组:
a := [3][4]int{
{0, 1, 2, 3}, /* 第一行索引为 0 */
{4, 5, 6, 7}, /* 第二行索引为 1 */
{8, 9, 10, 11}, /* 第三行索引为 2 */
}数组是值类型
Go 中的数组是值类型,而不是引用类型。这意味着当它们被分配给一个新变量时,将把原始数组的副本分配给新变量。如果对新变量进行了更改,则不会在原始数组中反映。
package main
import "fmt"
func main() {
a := [...]string{"USA", "China", "India", "Germany", "France"}
b := a // a copy of a is assigned to b
b[0] = "Singapore"
fmt.Println("a is ", a)
fmt.Println("b is ", b)
}运行结果:
a is [USA China India Germany France]
b is [Singapore China India Germany France]数组的大小是类型的一部分。因此 [5]int 和 [25]int 是不同的类型。因此,数组不能被调整大小。不要担心这个限制,因为切片的存在就是为了解决这个问题。
package main
func main() {
a := [3]int{5, 78, 8}
var b [5]int
b = a // not possible since [3]int and [5]int are distinct types
}切片
什么是切片
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活、功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
切片是一种方便、灵活且强大的包装器。切片本身没有任何数据,它们只是对现有数组的引用。
切片与数组相比,不需要设定长度,在 [] 中不用设定值,相对来说比较自由。
从概念上面来说 slice 像一个结构体,这个结构体包含了三个元素:
- 指针:指向数组中 slice 指定的开始位置
- 长度:即 slice 的长度
- 最大长度:也就是 slice 开始位置到数组的最后位置的长度
数组与切片的区别
| 特性 | 数组 | 切片 |
|---|---|---|
| 长度 | 固定,是类型的一部分 | 动态,可变 |
| 类型 | 值类型 | 引用类型 |
| 定义方式 | var a [SIZE]type | var s []type |
| 创建函数 | 直接声明初始化 | 可使用 make() 创建 |
| 容量扩展 | 不支持 | 支持 append() 动态扩容 |
| 比较操作 | 同类型同长度可 == 比较 | 只能与 nil 比较 |
| 赋值行为 | 复制整个数组副本 | 复制引用,指向同一底层数组 |
| 适用场景 | 元素数量已知且固定 | 元素数量动态变化 |
切片的语法
定义切片
var identifier []type切片不需要说明长度。
或使用 make() 函数来创建切片:
var slice1 []type = make([]type, len)
// 也可以简写为
slice1 := make([]type, len)make([]T, length, capacity)初始化
s[0] = 1
s[1] = 2
s[2] = 3s := []int{1, 2, 3}s := arr[startIndex:endIndex]将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片(前闭后开),长度为 endIndex - startIndex。
s := arr[startIndex:]缺省 endIndex 时将表示一直到 arr 的最后一个元素。
s := arr[:endIndex]缺省 startIndex 时将表示从 arr 的第一个元素开始。
package main
import (
"fmt"
)
func main() {
a := [5]int{76, 77, 78, 79, 80}
var b []int = a[1:4] // creates a slice from a[1] to a[3]
fmt.Println(b)
}修改切片
slice 没有自己的任何数据,它只是底层数组的一个表示。对 slice 所做的任何修改都将反映在底层数组中。
示例代码:
package main
import (
"fmt"
)
func main() {
darr := [...]int{57, 89, 90, 82, 100, 78, 67, 69, 59}
dslice := darr[2:5]
fmt.Println("array before", darr)
for i := range dslice {
dslice[i]++
}
fmt.Println("array after", darr)
}运行结果:
array before [57 89 90 82 100 78 67 69 59]
array after [57 89 91 83 101 78 67 69 59]当多个切片共享相同的底层数组时,每个元素所做的更改将在数组中反映出来。
示例代码:
package main
import (
"fmt"
)
func main() {
numa := [3]int{78, 79, 80}
nums1 := numa[:] // creates a slice which contains all elements of the array
nums2 := numa[:]
fmt.Println("array before change 1", numa)
nums1[0] = 100
fmt.Println("array after modification to slice nums1", numa)
nums2[1] = 101
fmt.Println("array after modification to slice nums2", numa)
}运行结果:
array before change 1 [78 79 80]
array after modification to slice nums1 [100 79 80]
array after modification to slice nums2 [100 101 80]len() 和 cap() 函数
切片的长度是切片中元素的数量。切片的容量是从创建切片的索引开始的底层数组中元素的数量。
切片是可索引的,并且可以由 len() 方法获取长度;切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
package main
import "fmt"
func main() {
var numbers = make([]int, 3, 5)
printSlice(numbers)
}
func printSlice(x []int) {
fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}运行结果:
len=3 cap=5 slice=[0 0 0]空切片
一个切片在未初始化之前默认为 nil,长度为 0:
package main
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
if numbers == nil {
fmt.Printf("切片是空的")
}
}
func printSlice(x []int) {
fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}运行结果:
len=0 cap=0 slice=[]
切片是空的package main
import "fmt"
func main() {
/* 创建切片 */
numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
printSlice(numbers)
/* 打印原始切片 */
fmt.Println("numbers ==", numbers)
/* 打印子切片从索引1(包含) 到索引4(不包含) */
fmt.Println("numbers[1:4] ==", numbers[1:4])
/* 默认下限为 0 */
fmt.Println("numbers[:3] ==", numbers[:3])
/* 默认上限为 len(s) */
fmt.Println("numbers[4:] ==", numbers[4:])
numbers1 := make([]int, 0, 5)
printSlice(numbers1)
/* 打印子切片从索引 0(包含) 到索引 2(不包含) */
number2 := numbers[:2]
printSlice(number2)
/* 打印子切片从索引 2(包含) 到索引 5(不包含) */
number3 := numbers[2:5]
printSlice(number3)
}
func printSlice(x []int) {
fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}运行结果:
len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]
numbers == [0 1 2 3 4 5 6 7 8]
numbers[1:4] == [1 2 3]
numbers[:3] == [0 1 2]
numbers[4:] == [4 5 6 7 8]
len=0 cap=5 slice=[]
len=2 cap=9 slice=[0 1]
len=3 cap=7 slice=[2 3 4]append() 和 copy() 函数
append 向 slice 里面追加一个或者多个元素,然后返回一个和 slice 一样类型的 slice。
copy 函数从源 slice 的 src 中复制元素到目标 dst,并且返回复制的元素的个数。
append 函数会改变 slice 所引用的数组的内容,从而影响到引用同一数组的其它 slice。但当 slice 中没有剩余空间(即 (cap - len) == 0)时,此时将动态分配新的数组空间。返回的 slice 数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的 slice 则不受影响。
package main
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
/* 允许追加空切片 */
numbers = append(numbers, 0)
printSlice(numbers)
/* 向切片添加一个元素 */
numbers = append(numbers, 1)
printSlice(numbers)
/* 同时添加多个元素 */
numbers = append(numbers, 2, 3, 4)
printSlice(numbers)
/* 创建切片 numbers1 是之前切片的两倍容量 */
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1, numbers)
printSlice(numbers1)
}
func printSlice(x []int) {
fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}运行结果:
len=0 cap=0 slice=[]
len=1 cap=2 slice=[0]
len=2 cap=2 slice=[0 1]
len=5 cap=8 slice=[0 1 2 3 4]
len=5 cap=12 slice=[0 1 2 3 4]
numbers1与numbers两者不存在联系,numbers发生变化时,numbers1是不会随着变化的。也就是说copy方法是不会建立两个切片的联系的。
Map
什么是 Map
map 是 Go 中的内置类型,它将一个值与一个键关联起来。可以使用相应的键检索值。
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的,也是引用类型。
使用 map 过程中需要注意的几点:
- map 是无序的,每次打印出来的 map 都会不一样,它不能通过 index 获取,而必须通过 key 获取
- map 的长度是不固定的,也就是和 slice 一样,也是一种引用类型
- 内置的
len函数同样适用于 map,返回 map 拥有的 key 的数量 - map 的 key 可以是所有可比较的类型,如布尔型、整数型、浮点型、复杂型、字符串型……也可以键
Map 的使用
使用 make() 创建 map
可以使用内建函数 make 也可以使用 map 关键字来定义 Map:
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
map_variable = make(map[key_data_type]value_data_type)rating := map[string]float32{"C": 5, "Go": 4.5, "Python": 4.5, "C++": 2}如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对。
package main
import "fmt"
func main() {
var countryCapitalMap map[string]string
/* 创建集合 */
countryCapitalMap = make(map[string]string)
/* map 插入 key-value 对,各个国家对应的首都 */
countryCapitalMap["France"] = "Paris"
countryCapitalMap["Italy"] = "Rome"
countryCapitalMap["Japan"] = "Tokyo"
countryCapitalMap["India"] = "New Delhi"
/* 使用 key 输出 map 值 */
for country := range countryCapitalMap {
fmt.Println("Capital of", country, "is", countryCapitalMap[country])
}
/* 查看元素在集合中是否存在 */
captial, ok := countryCapitalMap["United States"]
/* 如果 ok 是 true, 则存在,否则不存在 */
if ok {
fmt.Println("Capital of United States is", captial)
} else {
fmt.Println("Capital of United States is not present")
}
}运行结果:
Capital of France is Paris
Capital of Italy is Rome
Capital of Japan is Tokyo
Capital of India is New Delhi
Capital of United States is not presentdelete() 函数
delete(map, key) 函数用于删除集合的元素,参数为 map 和其对应的 key。删除函数不返回任何值。
package main
import "fmt"
func main() {
/* 创建 map */
countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New Delhi"}
fmt.Println("原始 map")
/* 打印 map */
for country := range countryCapitalMap {
fmt.Println("Capital of", country, "is", countryCapitalMap[country])
}
/* 删除元素 */
delete(countryCapitalMap, "France")
fmt.Println("Entry for France is deleted")
fmt.Println("删除元素后 map")
/* 打印 map */
for country := range countryCapitalMap {
fmt.Println("Capital of", country, "is", countryCapitalMap[country])
}
}运行结果:
原始 map
Capital of France is Paris
Capital of Italy is Rome
Capital of Japan is Tokyo
Capital of India is New Delhi
Entry for France is deleted
删除元素后 map
Capital of Italy is Rome
Capital of Japan is Tokyo
Capital of India is New Delhiok-idiom(判断 key 是否存在)
我们可以通过 key 获取 map 中对应的 value 值。语法为:
map[key]但是当 key 如果不存在的时候,我们会得到该 value 值类型的默认值,比如 string 类型得到空字符串,int 类型得到 0。但是程序不会报错。
所以我们可以使用 ok-idiom 获取值,可知道 key/value 是否存在:
value, ok := map[key]示例代码:
package main
import (
"fmt"
)
func main() {
m := make(map[string]int)
m["a"] = 1
x, ok := m["b"]
fmt.Println(x, ok)
x, ok = m["a"]
fmt.Println(x, ok)
}运行结果:
0 false
1 truemap 的长度
使用 len 函数可以确定 map 的长度。
len(map) // 可以得到map的长度map 是引用类型
与切片相似,映射是引用类型。当将映射分配给一个新变量时,它们都指向相同的内部数据结构。因此,一个的变化会反映另一个。
示例代码:
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("Original person salary", personSalary)
newPersonSalary := personSalary
newPersonSalary["mike"] = 18000
fmt.Println("Person salary changed", personSalary)
}运行结果:
Original person salary map[steve:12000 jamie:15000 mike:9000]
Person salary changed map[steve:12000 jamie:15000 mike:18000]map 不能使用
==操作符进行比较。==只能用来检查 map 是否为空。否则会报错:invalid operation: map1 == map2 (map can only be compared to nil)
字符串
什么是 string
Go 中的字符串是一个字节的切片。可以通过将其内容封装在 "" 中来创建字符串。Go 中的字符串是 Unicode 兼容的,并且是 UTF-8 编码的。
示例代码:
package main
import (
"fmt"
)
func main() {
name := "Hello World"
fmt.Println(name)
}访问字符串中的单个字节
package main
import (
"fmt"
)
func main() {
s := "Hello World"
for i := 0; i < len(s); i++ {
fmt.Printf("%d ", s[i])
}
fmt.Printf("\n")
for i := 0; i < len(s); i++ {
fmt.Printf("%c ", s[i])
}
}运行结果:
72 101 108 108 111 32 87 111 114 108 100
H e l l o W o r l dstrings 包
访问 strings 包,可以有很多操作 string 的函数。常用的函数包括:
| 函数 | 说明 |
|---|---|
strings.Contains(s, substr) | 判断字符串 s 是否包含子串 substr |
strings.HasPrefix(s, prefix) | 判断字符串 s 是否以 prefix 开头 |
strings.HasSuffix(s, suffix) | 判断字符串 s 是否以 suffix 结尾 |
strings.Index(s, substr) | 返回子串 substr 在 s 中第一次出现的索引 |
strings.Replace(s, old, new, n) | 将 s 中的 old 替换为 new,n 为替换次数 |
strings.Split(s, sep) | 以 sep 为分隔符分割字符串 s |
strings.Join(a, sep) | 以 sep 为分隔符连接字符串切片 a |
strings.ToLower(s) | 将字符串转为小写 |
strings.ToUpper(s) | 将字符串转为大写 |
strings.TrimSpace(s) | 去除字符串首尾空白字符 |
strconv 包
访问 strconv 包,可以实现 string 和其他数值类型之间的转换。常用函数包括:
| 函数 | 说明 |
|---|---|
strconv.Atoi(s) | 字符串转 int |
strconv.Itoa(i) | int 转字符串 |
strconv.ParseFloat(s, bitSize) | 字符串转 float |
strconv.FormatFloat(f, fmt, prec, bitSize) | float 转字符串 |
strconv.ParseBool(s) | 字符串转 bool |
strconv.FormatBool(b) | bool 转字符串 |