数组、字符串和切片
数组、字符串和切片是Go语言中常用的三种数据类型,它们在底层原始数据有着相同的内存结构,在上层,因为语法的限制而有着不同的行为表现。
数组
Go语言的数组和其他语言中的数组的操作基本一致,是由由固定长度的特定类型元素组成的序列,正是因为固定长度,导致其很少被使用。
数组的定义如下
var a [3]int // 定义长度为 3 的 int 型数组, 元素全部为 0
var b = [...]int{1, 2, 3} // 定义长度为 3 的 int 型数组, 元素为 1, 2, 3
var c = [...]int{2: 3, 1: 2} // 定义长度为 3 的 int 型数组, 元素为 0, 2, 3
var d = [...]int{1, 2, 4: 5, 6} // 定义长度为 6 的 int 型数组, 元素为 1, 2, 0, 0, 5, 6
Go 语言中数组是值语义。一个数组变量即表示整个数组,它并不是隐式的指向第一个元素的指针(比如 C 语言的数组),而是一个完整的值。
这句话如何理解,我们知道在C++语言中定义一个数组,这个数组a并不表示整个数组,它是指向该数组第一个元素的指针,在下面的例子中可以通过打印
a
a
a 来查看其地址。但是在Go语言中
a
a
a 就表示整个数组,所以可以把数组看成是一种特殊类型的结构体。
#include<iostream>
using namespace std;
int main()
{
int a[5] = { 1,2,3,4,5 };
cout << a << endl; // 000000D3AD2FF758
return 0;
}
var a = [...]int{1, 2, 3} // 定义长度为 3 的 int 型数组, 元素为 1, 2, 3
var b = &a
fmt.Println(a) // [1 2 3]
fmt.Println(b) // &[1 2 3]
同样的,Go语言中的数组可以用于不同的数据类型,如字符串数组、结构体数组、函数数组、接口数组、管道数组等。数组的遍历也比较简单这里就不再赘述了。
字符串
字符串是一个长度固定、只读的字节数组。如果你想通过下标的方式修改对应下表的值时,就会提示报错cannot assign to s[0] (value of type byte)
。如果非要修改要将将 string 转为 []byte 修改后,再转为 string ,具体代码如下。
func main() {
str := "hello world"
strBytes := []byte(str)
strBytes[0] = 'H'
str = string(strBytes)
fmt.Println(str)
}
字符串结构由两个信息组成:第一个是字符串指向的底层字节数组,第二个是字符串的字节的长度。下面是字符串“Hello, world”本身对应的内存结构。data 代表指向的底层的字节数组,len 为长度。
字符串虽然不是切片,但是支持切片操作,可以通过索引来得到对应的字符串
s := "hello, world"
hello := s[:5]
world := s[7:]
s1 := "hello, world"[:5]
s2 := "hello, world"[7:]
切片
Go语言中的切片和C++STL中的vector非常相似,都是动态数组。结构定义如下
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
其中Cap表示最大容量。如果未指定容量,则默认为与长度相同。
// 切片的定义
var (
a []int // nil 切片, 和 nil 相等, 一般用来表示一个不存在的切片
b = []int{} // 空切片, 和 nil 不相等, 一般用来表示一个空的集合
c = []int{1, 2, 3} // 有 3 个元素的切片, len 和 cap 都为 3
d = c[:2] // 有 2 个元素的切片, len 为 2, cap 为 3
e = c[0:2:cap(c)] // 有 2 个元素的切片, len 为 2, cap 为 3
f = c[:0] // 有 0 个元素的切片, len 为 0, cap 为 3
g = make([]int, 3) // 有 3 个元素的切片, len 和 cap 都为 3
h = make([]int, 2, 3) // 有 2 个元素的切片, len 为 2, cap 为 3
i = make([]int, 0, 3) // 有 0 个元素的切片, len 为 0, cap 为 3
)
// 切片的遍历,和数组类似
for i := range a {
fmt.Printf("a[%d]: %d\n", i, a[i])
}
for i, v := range b {
fmt.Printf("b[%d]: %d\n", i, v)
}
for i := 0; i < len(c); i++ {
fmt.Printf("c[%d]: %d\n", i, c[i])
}
切片的增加、删除操作
var a []int
a = append(a, 1) // 追加 1 个元素
a = append(a, 2, 3, 4) // 追加多个元素, 手写解包方式
a = append(a, []int{5, 6, 7, 8, 9}...) // 追加 1 个切片, 切片需要解包
a = append([]int{0}, a...) // 在开头添加 1 个元素
i := 5
x := 6
a = append(a[:i], append([]int{x}, a[i:]...)...) // // 在第 i 个位置插入 x
a = append(a, 0) // 切片扩展 1 个空间
copy(a[i+1:], a[i:]) // a[i:] 向后移动 1 个位置
a[i] = x // 设置新添加的元素
y := []int{2, 3, 3}
a = append(a, y...) // 为 x 切片扩展足够的空间
copy(a[i+len(y):], a[i:]) // a[i:] 向后移动 len(x) 个位置
copy(a[i:], y) // 复制新添加的切片
N := 2
b := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
b = b[:len(b)-1] // 删除尾部 1 个元素
b = b[:len(b)-N] // 删除尾部 N 个元素
b = append(b[:0], b[1:]...) // 删除开头 1 个元素
b = append(b[:0], b[N:]...) // 删除开头 N 个元素
b = b[:copy(b, b[1:])] // 删除开头 1 个元素
b = b[:copy(b, b[N:])] // 删除开头 N 个元素
b = append(b[:i], b[i+1:]...) // 删除中间 1 个元素
b = append(b[:i], b[i+N:]...) // 删除中间 N 个元素
b = b[:i+copy(b[i:], b[i+1:])] // 删除中间 1 个元素
b = b[:i+copy(b[i:], b[i+N:])] // 删除中间 N 个元素