1. 数组
数组是一组具有相同数据类型在内存中有序存储的数据集合
特点
- 长度固定,不能修改
- 赋值和函数传递过程是值复制,涉及到内存 copy,性能低下
- 数组的定义和使用
声明数组: var 数组名 [SIZE] 数组变量类型
初始化数组:
var arr= [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0} 或者 arr:=[5]int{1,2,3,4,5}
我们也可以通过字面量在声明数组的同时快速初始化数组:
balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0} 或 balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
如果设置了数组的长度,我们还可以通过指定下标来初始化元素:
// 将索引为 1 和 3 的元素初始化 balance := [5]float32{1:2.0,3:7.0}
初始化数组中 {} 中的元素个数不能大于 [] 中的数字。如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:
- go同样也有多维数组
声明方式:var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
初始化二维数组:
a := [3][4]int{
{0, 1, 2, 3} , /* 第一行索引为 0 */
{4, 5, 6, 7} , /* 第二行索引为 1 */
{8, 9, 10, 11}, /* 第三行索引为 2 */
//注意:以上代码中倒数第二行的 } 必须要有逗号,因为最后一行的 } 不能单独一行,也可以写成这样:
}
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() {
// 创建空的二维数组
animals := [][]string{}
// 创建三一维数组,各数组长度不同
row1 := []string{"fish", "shark", "eel"}
row2 := []string{"bird"}
row3 := []string{"lizard", "salamander"}
// 使用 append() 函数将一维数组添加到二维数组中
animals = append(animals, row1)
animals = append(animals, row2)
animals = append(animals, row3)
// 循环输出
for i := range animals {
fmt.Printf("Row: %v\n", i)
fmt.Println(animals[i])
}
}
2. 切片
切片(slice)是一组具有相同数据类型在内存中有序存储的可扩容的数据集合
- slice声明方式
var 切片名 []数据类型
var slice []int
make([]数据类型,长度)
var slice []int = make([]int, 10)
当使用var slice []int 是此时的切片实际上是一个nil,还没有被开辟空间,只是有了一个slice的“引用”。而使用make的方式时会为我们的slice开辟你传入的大小的空间。
func main() { var slice []int slice[0] = 123 fmt.Println(slice) }
上面的代码是一个错误的代码 在运行的时候会报错越界问题。
- slice的len和cap
我们可以通过len() 和cap()函数来求取我们的slice的长度和容量(长度就是我们当前slice中存了多少了元素,而容量是slice最大可以容纳的元素数量。)
在go源码中当length小于1024的时候是按照2倍来扩容的,当超过1024的时候每次是以前的1.25倍。
func main() {
/*var slice []int
slice[0] = 123
fmt.Println(slice)*/
var s1 = make([]int, 2)
//slice自动扩容
s1 = append(s1, 1)
s1 = append(s1, 2)
s1 = append(s1, 1)
s1 = append(s1, 1)
fmt.Printf("%v\n", s1)
//现在我们来查看len cap
println(len(s1)) //6 0 0 1 2 1 1
println(cap(s1)) //8
}
- 切片的截取
切片在截取的时候返回的新的切片是指向原来的切片的内存地址的。感觉类似于浅拷贝,改变截取后的切片的值也会同步的改变原切片的值。
func main() {
//切片的截取
slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
//切片名[起始下标:结束下标:容量] 左闭右开 包含起始下标 不包含结束下标
//s := slice[2:7]
//fmt.Println(s)
//s:=slice[2:]
//fmt.Println(s)
//s:=slice[:5]
//fmt.Println(s)
s := slice[2:5:6] //实际容量=容量-起始下标
fmt.Println(s)
//fmt.Println(len(s))
//fmt.Println(cap(s))
s[0] = 333
//切片的截取 是将新的切片指向源切片的内存地址 修改一个会影响另外一个
fmt.Println(s)
fmt.Println(slice)
fmt.Println(unsafe.Sizeof(slice))
fmt.Println(unsafe.Sizeof(s))
}
利用切片的属性还可以简单的实现一个CRUD
func test6() {
//slice crud
var nums = []int{1, 2, 3, 4, 5}
fmt.Printf("nums = %v\n", nums)
//新增
nums = append(nums, 6)
fmt.Printf("新增后nums = %v\n", nums)
//删除
//比如删除我们切片中的下表为2的元素
s1 := append(nums[:2], nums[3:]...)
fmt.Printf("删除后nums = %v\n", s1)
//改
nums[2] = 222
fmt.Printf("修改后nums = %v\n", nums)
//查找
//给定一个key 查找下标然后返回 找不到返回-1
var key = 3
for i, num := range nums {
if num == key {
fmt.Printf("找到%d了,下表为%d:", key, i)
}
}
}
3. 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)
m1 := map[string]string{
"name": "zhangsan",
"age": "22",
}
fmt.Println(m1)
m2 := make(map[string]int) //m2 == empty map
var m3 map[string]int //m3 == nil
fmt.Print(m2, m3)
fmt.Print("遍历...\n")
for i, v := range m1 {
fmt.Println(i, v)
}
- 底层基于 Hash 实现,基于 Key-Value,无序的数据集合
- dict := make(map[T_KEY]T_VALUE) // key 的类型需要具备 == 和 != 操作 (除了slice、map、function的内建类型都可以作为key)
- 函数类型、字典类型和切片类型不能作为 key,不支持的操作类型会导致 panic
- 检测值是否存在
name := m1["name"]
fmt.Println(name)
//可以返回一个ok值 判断我们的key是否正确
nema, ok := m1["nema"] //找不到这个键 就会返回false
fmt.Print(nema, ok)
//所以可以改进代码
if name, ok := m1["name"]; ok == true {
fmt.Println(name)
}
- var m map[string]int // nil 类型,添加和修改会导致 panic
- nil: len/map[key]/delete(m, key) // 可以正常工作
- map 默认并发不安全,多个 goroutine 写同一个 map,引发竞态错误, go run –race 或者 go build - race
- map 对象即使删除了全部的 key,但不会缩容空间
4.指针
只要将数据存储在内存中都会为其分配内存地址。内存地址使用十六进数据表示。内存为每一个字节分配一个 32 位或 64 位的编号(与 32 位或者 64 位处理器相关)。可以使用运算符 & (取地址运算符)来获取数据的内存地址。
func main() {
a := 10
//取出变量a在内存的地址
//fmt.Println(&a)
var p *int = &a
//fmt.Println(p)
//fmt.Println(&a)
//通过指针间接修改变量的值
*p = 132
fmt.Println(a)
//const MAX int = 100
//fmt.Println(&MAX)//err 不可以获取常量的内存地址
}
func main() {
//定义指针 默认值为nil 指向内存地址编号为0的空间 内存地址0-255为系统占用 不允许用户读写操作
//var p *int = nil
//*p = 123 //err
//fmt.Println(*p)
//开辟数据类型大小的空间 返回值为指针类型
//new(数据类型)
var p *int
p = new(int)
*p = 123
fmt.Println(*p)
}