Go 语言入门指南:基础语法和常用特性解析 (二)
入门 Go 语言,走近 Go 语言,体会 Go 语言的独特魅力。从基础语法和常用特性解析入手...
我们继续从 字符串 数组 slice map struct 淦(肝)起,本文同样引自 CSDN 中 Go 语言入门指南:基础语法和常用特性解析_框住的博客-CSDN博客 的文章,让我们畅游在 Go 语言的海洋里吧,Go!
五、字符串
- Go 语言字符串按字节存储,不同字符占用不同数目的字节。
- 字符串的字符按 unicode 编码存储,不同的字符按 1~4 个字节存储。其中,中文汉字占用 3 个字节,英文占用 1 个字节。
- 字符串索引访问是按字节访问的,而不是字符。
5.1 rune
unicode通常用 4 个字节来表示,对应Go语言的字符rune占 4 个字节rune类型是一个衍生类型,在内存里面使用int32类型的 4 个字节存储
type rune int32
5.2 Unicode 和 UTF-8
- Unicode 支持超过一百种的语言和十万字符的码点。
- UTF-8 是 Unicode 标准的一种实现。
- UTF-8 特点:
- 字符长度可变,长度1到4字节不等。
- 第一个 bit 为0,那么长度为1字节,使用剩余7位存放字符,正好能覆盖 ASCII 码字符集。
- 前两位是10,则表示长度为两个字节,第二个字节以0开头。
- 对于三个字节的 UTF-8 码。这三个字节分别是110、10和0。
5.3 按字节访问
package main
import "fmt"
func main() {
var s = "嘻哈china"
for i:=0;i<len(s);i++ {
fmt.Printf("%d %x ", i, s[i])
}
}
-----------
0 e5 1 98 2 bb 3 e5 4 93 5 88 6 63 7 68 8 69 9 6e 10 61
5.4 按字符rune访问
package main
import "fmt"
func main() {
var s = "嘻哈china"
for codepoint, runeValue := range s {
fmt.Printf("%d %d ", codepoint, int32(runeValue))
}
}
-----------
0 22075 3 21704 6 99 7 104 8 105 9 110 10 97
对字符串进行 range 遍历,每次迭代出两个变量 codepoint 和 runeValue。
- codepoint:字符起始位置
- runeValue:对于的 unicode 编码(类型是rune)
5.5 特点
- 字符串是只读的,不支持按索引修改。
- 字符串支持切片成子串。
字节数组和字符串的转换
借助 []byte() 和 string() 即可:
var s1 = "hello world"
var b = []byte(s1) // 字符串转字节数组
var s2 = string(b) // 字节数组转字符串
/*
b = [104 101 108 108 111 32 119 111 114 108 100]
s2 = hello world
*/
六、数组
在 Go 语言中,由于数组长度不可变,数组的使用频率并不高。大部分时候使用 slice(切片),因为 slice 比数组更灵活,长度是可变的。
6.1 数组的定义
6.1.1 使用默认初始值
var a [3]int
上述代码定义了长度为 3 的整型数组,三个元素都是默认初始值 0。
6.1.2 定义并初始化
var b [3]int = [3]int{1, 2, 3}
var b = [3]int{1, 2, 3}
6.1.3 省略数组长度
c := [...]int{1, 2, 3}
上述代码的数组长度由后面元素的个数决定。这里数组的长度为 3。
6.1.4. 索引赋值初始化
d := [...]int{4, 4:1, 1:2}
// d : {4, 2, 0, 0, 1}
- 并没有给所有的元素赋值,而且赋值使用了
index:value的方式。4:1 的意思是给下标为 4 的元素赋值为 1。 - 数组长度根据最大下标确定。
6.2 数组特点
[3]int和[4]int是两种不同的数据类型,这两种不同的数组在编译时就已经确定。把[4]int类型的变量赋值给[3]int类型的变量会报错。- Go 语言数组长度是数组的一部分,并非向 C 语言那样只存储数组的地址。
- 在分配内存底层空间时,数组的元素是紧挨着分配到固定位置的,这也是数组长度不可变的原因。
6.3 数组是默认按值传递
- Go语言数组是传递值的,不同于其他语言的数组都是传递引用的。
- 值传递涉及数组的拷贝,会浪费性能,但形参数组的修改不会影响原数组。
给出一个实例 ——修改数组的0索引位置的元素
| 方式 | 解释 |
|---|---|
| 直接传数组 | 传数组地址 |
| 值传递 | 引用传递(指针) |
package main
import "fmt"
func main() {
var a [3]int = [3]int{1, 2, 3}
change_arr_by_value(a)
fmt.Println(a[0]) // 1 按值传递,修改失败
change_arr_by_reference(&a)
fmt.Println(a[0]) // 999 按索引传递,修改成功
}
func change_arr_by_value(arr [3]int) {
arr[0] = 999
}
func change_arr_by_reference(arr *[3]int) {
arr[0] = 999
}
七、slice
slice(切片)是一个拥有相同类型元素的可变长序列,且 slice 定义和数组的定义很像。
- slice是通过指向底层数组来存储数据,而且可能有多个slice指向同一个底层数组。如果有任一切片指向这个数组,底层数组就因处于使用状态而无法被删除。
- 极端情况,很小的切片指向很大底层数组,会造成空间的浪费。
7.1 原型
type SliceHeader struct {
Data uintptr // 指针
Len int // 长度
Cap int // 容量
}
说明
- 指针指向 slice 开始访问的第一个元素。
- 长度是切片的长度,及元素个数。
- 容量是切片能访问的最大元素个数。
slice 的容量 ≥ silice 的长度
切片的底层是数组,Go 语言的切片对应着底层数组。一个底层数组可以对应多个 slice。
7.2 基本操作
7.2.1 定义
s := []int{1, 2, 3, 4, 5}
如果在方括号中指定了长度或写入省略号,那就是一个数组。
ss := make([]int, 10)
make 函数定义了 []int 类型的切片,长度为 10,元素默认值为 0。
7.2.2 释放slice
ss = nil
如果将 slice 赋值为 nil,垃圾回收机制将会回收 slice 的空间。
7.2.3 获取长度和容量
len(slice)
cap(slice)
7.2.4 切片
- 切片
s[i:j]是左闭右开,索引区间是[i, j)。 - 左边界 i 不写,默认是 0。右边界 j 不写,默认是切片的容量 cap(slice)。
s[:]是整个切片。
s := []int{0, 1, 2, 3, 4, 5}
s[0:3] // {0, 1, 2}
s[:] // {0, 1, 2, 3, 4, 5}
7.2.5 数组的切片
- 对数组切片的修改会影响原数组。slice默认是引用传递
a := [...]int{1, 2, 3, 4, 5}
ss := a[1:3]
ss[0] = 99
ss[1] = -99
fmt.Println(ss) // [99, -99]
fmt.Println(a) // [1, 99, -99, 4, 5]
7.3 append
- slice 的长度是动态的。
- append 可以在原来的切片上插入元素,可以在开头、结尾、指定位置插入元素或其他 slice。
- 数组不能直接插入,要转换成切片才能添加。
s = []int{1, 2, 3} // 定义切片
arr = [3]int{66, 77, 88} // 定义数组
7.3.1 头插
ss = append(a[:], ss...)
ss = append([]int{1}, ss...)
注意
ss...:把切片 ss,拆解成一个个元素。之后将依次插入。a[:]:取数组的切片。
7.3.2 尾插
ss = append(ss, 1)
ss = append(ss, a[:]...)
7.3.3 任意位置插入
ss = append(ss[:1], append(a[:], ss[1:]...)...) // 在 ss 的索引为 1 的位置插入
7.4 slice的增长过程
- 切片的底层是数组,如果使用append函数向切片中添加元素是,实际上是把元素放到切片的底层数组。
- 如果底层数组满了,没有空间,这时再向切片添加元素,Go只能创建一个更大的新数组,将原来的数组复制到新数组,并把新数组作为切片的底层数组。
7.4.1 代码演示
s1 := []int{1, 2, 3}
fmt.Printf("长度:%d,容量:%d,数组地址:%p\n", len(s1), cap(s1), s1)
s1 = append(s1, 4)
fmt.Printf("长度:%d,容量:%d,数组地址:%p\n", len(s1), cap(s1), s1)
s1 = append(s1, 5)
fmt.Printf("长度:%d,容量:%d,数组地址:%p\n", len(s1), cap(s1), s1)
s1 = append(s1, 6)
fmt.Printf("长度:%d,容量:%d,数组地址:%p\n", len(s1), cap(s1), s1)
s1 = append(s1, 7)
fmt.Printf("长度:%d,容量:%d,数组地址:%p\n", len(s1), cap(s1), s1)
s1 = append(s1, 8)
fmt.Printf("长度:%d,容量:%d,数组地址:%p\n", len(s1), cap(s1), s1)
/*output
长度:3,容量:3,数组地址:0xc00001e0c0
长度:4,容量:6,数组地址:0xc00001a120
长度:5,容量:6,数组地址:0xc00001a120
长度:6,容量:6,数组地址:0xc00001a120
长度:7,容量:12,数组地址:0xc00005c060
长度:8,容量:12,数组地址:0xc00005c060
*/
7.4.3 切片变换过程
7.5 copy
切片元素的赋值可利用 copy 函数。
a1 := []{1,1,1,1,1}
b1 := []{-1,-1,-1}
copy(a1, b1) // 将 b1 复制到 a1
// a1 : [-1,-1,-1,1,1]
a2 := []{2,2,2,2,2}
b2 := []{-2,-2,-2}
copy(b2, a2) // 将 b1 复制到 a1
// b2 : [2,2,2]
注意 要理解复制到的含义。
copy函数只涉及元素值的拷贝,不涉及新空间的创造。- copy 的两个参数必须是 slice,不能是数组。
八、map
- map(映射)是 Go 语言提供的
key-value(键值对)形式的无序集合。 - map 的底层是 Hash(哈希)表。
- 键值对的键有唯一性的要求,通过键来获取值和更新值。
8.1 定义形式
map[k]v
- k 是键,同一 mao 中所有的 key 是同一数据类型,而且必须能进行
==的比较运算 。 - boll 类型作为 k 并不灵活,而浮点型作为 k,可能会因为精度问题而导致异常。
8.2 ## 基本操作
8.2.1 创建 map
// 方式一
m1 := make(map[string]int)
// 方式二
m2 := map[string]int{
"k1" : 11,
"k2" : 22,
}
8.2.2 访问元素
map[k]
比如 val,ok = m2["k1"]。根据 key 找 val,如果 key 不存在,ok=false。
8.2.3 添加元素
map[key] = value
注意:key 如果存在,则用 value 更新值。如果不存在,则添加 key-value 对。
8.2.4 删除元素
delete(map, k)
比如 delete[m2, "k2"]
注意 delete 函数删除不存在的 key 时,不会出错。
8.2.5 遍历map
for k, v := range m2 {
fmt.Println(k, v)
}
8.3 特点
- map 在元素赋值之前必须初始化。使用上面两种方式均可。如果只声明 map,使用则会出错。
- map 的默认初始值是 nil,未初始化的 map 是 nil。尽管如此,未初始化的 map 执行删除元素、len 操作、range 操作或查找元素时都不会报错。但未初始化之前进行元素赋值操作会出错。
var m1 map[string]int
m1["k1"] = 12 // error: assignment to nil map
九、struct
struct是复合类型,而非引用类型。复合类型是值传递,而引用类型是引用传递。
9.1 结构体定义
type Person struct {
name string
gender int
age int
}
var p1 Person
p2 := Person{"Jack", 1, 18}
p3 := Person{name:"Scott", gender:1, age:18}
注意 struct成员的可见性是通过首字母大小写控制,首字母小写仅本报可见,首字母大写则包外也可访问。
9.2 成员访问
p3.name
9.3 结构体指针
var pp *Person
注意 结构体指针必须初始化后才可以使用,因为如果仅仅声明结构体指针类型变量,其默认值初始值是 nil。
| 结构体指针 | 解释 |
|---|---|
pp := new(Person) | 创建指向 Person 结构体的指针 pp |
pp.name = "Json" | 结构体指针访问成员也是直接.运算符,这是 Go 的语法糖 |
9.4 make和new
make 函数用于 slice、map 和 chan 进行内存分配,它返回的不是指针,而是上面三个类型中的某一个类型本身。new 函数返回初始化的类型对应的指针,new 函数主要用于 struct 初始化中,其他场景应用较少。