数组
(1)定义数组的几种方式
func test1() {
var a [3]int
var b = [...]int{1, 2, 3}
var c = [...]int{2: 3, 1: 2}
var d = [...]int{1, 2, 4: 5, 6}
fmt.Print(a,b,c,d)
}
第一种方式是定义一个数组变量的最基本的方式,数组的长度明确指定,数组中的 每个元素都以零值初始化。 第二种方式定义数组,可以在定义的时候顺序指定全部元素的初始化值,数组的长 度根据初始化元素的数目自动计算。 第三种方式是以索引的方式来初始化数组的元素,因此元素的初始化值出现顺序比 较随意。这种初始化方式和 map[int]Type 类型的初始化语法类似。数组的长度 以出现的最大的索引为准,没有明确初始化的元素依然用0值初始化。 第四种方式是混合了第二种和第三种的初始化方式,前面两个元素采用顺序初始 化,第三第四个元素零值初始化,第五个元素通过索引初始化,最后一个元素跟在 前面的第五个元素之后采用顺序初始化。
Go语言中数组是值语义。一个数组变量即表示整个数组,它并不是隐式的指向第一个元素的指针(比如C语言的数组),而是一个完整的值。当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。如果数组较大的话,数组的赋值也会有较大的开销。为了避免复制数组带来的开销,可以传递一个指向数组的指针,但是数组指针并不是数组。
(2)遍历数组的几种方式
func iterateArr() {
var arr = [...]int{1, 3, 4, 6, 8, 4}
for i := range arr {
fmt.Printf("数组下标:%d 数组元素:%d ", i, arr[i])
}
fmt.Println()
for i, v := range arr {
fmt.Printf("数组下标:%d 数组元素:%d ", i, v)
}
fmt.Println()
for i := 0; i < len(arr); i++ {
fmt.Printf("数组下标:%d 数组元素:%d ", i, arr[i])
}
}
用 for range 方式迭代的性能可能会更好一些,因为这种迭代可以保证不会出现数组越界的情形,每轮迭代对数组元素的访问时可以省去对下标越界的判断。
字符串
(1)数据结构
Go语言字符串的底层结构在 reflect.StringHeader 中定义:
type StringHeader struct {
Data uintptr
Len int
}
字符串结构由两个信息组成:第一个是字符串指向的底层字节数组,第二个是字符串的字节的长度。字符串其实是一个结构体,因此字符串的赋值操作也就是 reflect.StringHeader 结构体的复制过程,并不会涉及底层字节数组的复制。
字符串“Hello, world”本身对应的内存结构: 分析可以发现,“Hello, world”字符串底层数据和以下数组是完全一致的:
var data = [...]byte{'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o' , 'r', 'l', 'd'}
(2)字符串的只读特性
string类型的数据是不可变的。一旦声明了一个string类型的标识符,无论是变量还是常量,那么该标识符所指代的数据在整个程序的生命周期内便无法被更改。
func test3() {
var str="Hello Go"
fmt.Printf("原始值:%s \n",str)
var str2=[]byte(str)
str2[0]='A'
fmt.Printf("切片的值:%s \n",str2)
fmt.Printf("原字符串更新后值:%s \n",str)
}
在上述示例中,通过对原字符串进行切片,然后修改切片后的值。通过输出的结果可以看到切片后的字符串发生了改变,但是原来的字符串没有改变,这说明字符串是不可变的,切片操作的数据是原字符串的复制并重新分配的底层存储,其操作不会影响原字符串的操作。
(3)rune和byte类型
字符串中的每一个元素叫做“字符”,在遍历或者单个获取字符串元素时可以获得字符。
Go语言的字符有以下两种: 一种是 uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。 另一种是 rune 类型,代表一个 UTF-8 字符,当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型等价于 int32 类型。
byte 类型是 uint8 的别名,对于只占用 1 个字节的传统 ASCII 编码的字符来说,完全没有问题,例如 var ch byte = ‘A’,字符使用单引号括起来。 在 ASCII 码表中,A 的值是 65,使用 16 进制表示则为 41,所以下面的写法是等效的:
var ch byte = 65 或 var ch byte = '\x41' //(\x 总是紧跟着长度为 2 的 16 进制数)
在书写 Unicode 字符时,需要在 16 进制数之前加上前缀\u或者\U。因为 Unicode 至少占用 2 个字节,所以我们使用 int16 或者 int 类型来表示。如果需要使用到 4 字节,则使用\u前缀,如果需要使用到 8 个字节,则使用\U前缀。
Unicode 包中内置了一些用于测试字符的函数,这些函数的返回值都是一个布尔值,如下所示(其中 ch 代表字符): 判断是否为字母:unicode.IsLetter(ch) 判断是否为数字:unicode.IsDigit(ch) 判断是否为空白符号:unicode.IsSpace(ch)
/**
字符串长度
*/
func strLen() {
var chinese = "Hello 中国" //5个英文字符 2个空格 2个中文汉字,UTF编码中文占3个字符
fmt.Println("直接使用len函数", len(chinese))
fmt.Println("转换byte类型后长度", len([]byte(chinese)))
fmt.Println("转换rune类型后长度", len([]rune(chinese)))
fmt.Println("使用utf8.RuneCountInString后长度", utf8.RuneCountInString(chinese))
}
切片(slice)
(1)定义和结构分析
切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内。 切片就是一种简化版的动态数组。因为动态数组的长度是不固定,切片的长度自然也就不能是类型的组成部分了。数组虽然有适用它们的地方,但是数组 的类型和操作都不够灵活,因此在Go代码中数组使用的并不多。而切片则使用得相 当广泛。
切片的结构定义, reflect.SliceHeader :
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
Data表示存储的数据; Len表示切片的元素个数 Cap 表示切片指向的内存空间的最大容量(对应元素的个数,而不是字节数)
【网上看到的一个可参考的易懂的描述】 Go语言中切片的内部结构包含地址、大小和容量,切片一般用于快速地操作一块数据集合,如果将数据集合比作切糕的话,切片就是你要的“那一块”,切的过程包含从哪里开始(切片的起始位置)及切多大(切片的大小),容量可以理解为装切片的口袋大小,如下图所示。
(2)切片几种定义方式
func sliceDefinition() {
var (
a []int
b = []int{}
c = []int{1, 2, 3}
d = c[:2]
e = c[0:2:cap(c)]
f = c[:0]
g = make([]int, 3)
h = make([]int, 2, 3)
i = make([]int, 0, 3)
)
print(a, b, c, d, e, f, g, h, i)
}
和数组一样,内置的 len 函数返回切片中有效元素的长度,内置的 cap 函数返回 切片容量大小,容量必须大于或等于切片的长度
(3)使用 make() 函数构造切片
如果需要动态地创建一个切片,可以使用 make() 内建函数,格式如下:
make( []Type, size, cap )
其中 Type 是指切片的元素类型,size 指的是为这个类型分配多少个元素,cap 为预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题。
func makeTest() {
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b)
fmt.Println(len(a), len(b))
fmt.Println(cap(a), cap(b))
}
(4)切片的遍历方式
func sliceIterate() {
var a = []int{1, 4, 5}
for i := range a {
fmt.Printf("b[%d]: %d \n", i, a[i])
}
for i, v := range a {
fmt.Printf("b[ %d]: %d \n", i, v)
}
for i := 0; i < len(a); i++ {
fmt.Printf("b [ %d]: %d \n", i, a[i])
}
}
(5)添加切片元素和删除切片元素
func sliceOperation() {
fmt.Println("在切片的尾部追加")
var a = []int{1, 4, 5}
a = append(a, 6)
a = append(a, 1, 2, 3)
a = append(a, []int{1, 2, 3}...)
fmt.Println(a)
fmt.Println("在切片的头部追加")
var b = []int{1, 4, 5}
b = append([]int{7}, b...)
fmt.Println(b)
fmt.Println("删除尾部元素")
var c = []int{1, 2, 3, 6, 5, 2}
c = c[:len(c)-1]
c = c[:len(c)-2]
fmt.Println(c)
fmt.Println("删除开头元素")
var d = []int{1, 2, 3, 6, 8, 1}
d = d[1:]
d = d[2:]
fmt.Println(d)
}
|