Golang_5_数据

五、数据

5.1 字符串

  • 默认值为 ""而不是nil

  • 换行字符串使用 `,前置缩进空格也属于字符串内容

func main() {
	s := `line\r\n,
	line 2`
	fmt.Println(s)
}
// 输出:
// line\r\n,
//	 line 2
  • 支持字符串间的比较
  • 允许以索引号访问字节数组,但不能获取元素地址
  • 分配超大字符串 ==92==

5.2 数组

  • 定义方式:

    func main() {
       var a [4]int
      
       b := [4]int{2, 5} // 未初始化的元素默认为0
      
       c := [4]int{5, 3: 10} // 指定索引位置初始化
      
       d := [...]int{1, 2, 3} // 编辑器按照初始化值数量确定数组长度
      
       e := [...]int{10, 3: 100} // e 长度为 4,因为设定了索引位置
      
       fmt.Println(a, b, c, d, e, len(e))
    }
    
  • 元素类型相同,长度也相同时才属于同一类型,才能直接赋值

    • 复制和传参操作都会复制整个数组
    func main() {
        a := [4]int{1, 2, 3, 4}
        b := [4]int{5, 6, 7, 8}
      
        a = b
      
        for i := range a {
            fmt.Println(a[i]) // 输出 5678
        }
    }
    
    func test(x *[2]int) {
       fmt.Printf("x: %p, %v\n", x, *x) 
       x[1] += 100
    }
      
    func main() {
       a := [2]int{10, 20}
       test(&a)
      
       fmt.Printf("x: %p, %v\n", &a, a) 
        // x: 0xc420016090, [10 20]
        // x: 0xc420016090, [10 120]
    }
    
  • 多维数组

    • 只有第一纬度允许使用 b := [...][2]int{}
    • 内置函数 len 和 cap 只返回第一维度的长度

5.3 切片

切片本身并非动态数组或数组指针。内部通过指针引用底层数组,设定相关属性将数据读写操作限定在指定区域内。

切片本身是个只读对象,工作机制类似数组指针的一种包装

func main() {
   x := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

   fmt.Println(x[:], len(x[:])) // 0 到 最后
   fmt.Println(x[2:5], len(x[2:5])) // 2 到 4 (前面取到后面取不到)
   fmt.Println(x[2:5:7], len(x[2:5:7]), cap(x[2:5:7])) // 2 开始 ,最小到 5,最大到 7,len是可读的写元素数量,cap表示切片所引用长度的真实长度
   fmt.Println(x[:5:7], len(x[:5:7]), cap(x[:5:7]))// 0开始
}
len是可读的写元素数量,cap表示切片所引用长度的真实长度

直接创建切片对象,使用make或显示初始化语句

func main() {
   s1 := make([]int, 3, 5) // 指定 len、cap
   s2 := make([]int, 3) // 省略 cap,和 len 相等
   s3 := []int{10, 20, 5: 30} // 初始化元素分配底层数组,并设置 len、cap
   
   fmt.Println(s1, len(s1), cap(s1))
   fmt.Println(s2, len(s2), cap(s2))
   fmt.Println(s3, len(s3), cap(s3))
}
  • 不支持比较操作,只能判断是否为nil
  • 可以获取元素地址,但不能直接用指针访问元素内容
  • make 支持运行期间动态指定数组长度

reslice

以切片为数据源,再此基础创建切片

切片中的修改操作对所有关联切片可见

func main() {
   d := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
   s1 := d[3:7]
   fmt.Println(s1) // [3 4 5 6]
   s2 := s1[1:3]
   fmt.Println(s2) // [4 5]

   fmt.Println("修改后:")
   for i := range s2 {
      s2[i] += 100
   }

   fmt.Println(d) // [0 1 2 3 104 105 6 7 8 9]
   fmt.Println(s1) // [3 104 105 6]
   fmt.Println(s2) // [104 105]
}

reslice实现一个简单的栈

func main() {
	stack := make([]int, 0, 5)

	push := func(x int) error {
		n := len(stack)
		if n == cap(stack) {
			return errors.New("stack is full")
		}
		stack = stack[:n+1]
		stack[n] = x
		return nil
	}

	pop := func() (int, error){
		n := len(stack)
		if n == 0 {
			return 0, errors.New("stack is empty")
		}
		x := stack[n-1]
		stack = stack[:n-1]

		return x, nil
	}

	for i := 0; i < 7; i++ {
		fmt.Printf("push %d: %v, %v\n", 100*i, push(100*i), stack)
	}

	for i := 0; i < 7; i++ {
		x, err := pop()
		fmt.Printf("pop: %d, %v, %v\n", x, err, stack)
	}
}

append

向切片尾部追加数据,返回新的切片对象

func main() {
   s := make([]int, 0, 5)
   s1 := append(s, 10)
   s2 := append(s1, 20, 30)

   fmt.Println(s, len(s), cap(s)) // [] 0 5
   fmt.Println(s1, len(s1), cap(s1)) // [10] 1 5
   fmt.Println(s2, len(s2), cap(s2)) // [10 20 30] 3 5
}

追加过程若超出cap限制,则会对新切片重新分配数组

func main() {
    s := make([]int, 0, 100)
    s1 := s[:2:4]
    s2 := append(s1, 1, 2, 3, 4, 5, 6)

    fmt.Printf("s1 - %p - %v\n", &s1[0], s1)
    fmt.Printf("s2 - %p - %v (超过限制的切片)\n", &s2[0], s2)
    fmt.Printf("s  - %p - %v\n",&s1[0], s[:10])
    fmt.Printf("s_cap: %d, s1_cap: %d, s2_cap: %d\n",cap(s), cap(s1), cap(s2))

}
// 输出如下:
// s1 - 0xc420086000 - [0 0]
// s2 - 0xc420018100 - [0 0 1 2 3 4 5 6] (超过限制的切片) 重新开辟了空间
// s  - 0xc420086000 - [0 0 0 0 0 0 0 0 0 0] 对超限的切片操作,原切片不受影响
// s_cap: 100, s1_cap: 4, s2_cap: 8

copy

两个切片之间复制数据,运行指向同一底层数组,允许目标区间重叠。最终所复制长度以较短的切片长度(len)为准

copy(s1, s2):s1 对应位置用 s2 来替换

例1:

s1 := []int{0, 1, 2, 3, 4, 5, 6}
s2 := []int{9, 9, 9}
n := copy(s1, s2)
  • n 返回一个int长度,这时为 3
  • copy后,s1变为 {9, 9, 9, 3, 4, 5, 6}
  • copy(s2, s1)s2 变为 {1, 2, 3}

例二:

func main() {
	s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	s1 := s[5:8]
	n := copy(s[4:], s1)
	fmt.Println("copy(s[4:], s1)之后的s1:", s1)
	fmt.Println( "n:", n, " s:",s)

	s2 := make([]int, 6)
	n = copy(s2, s)
    fmt.Println("n:", n, "s2:", s2)
}

输出:

s1: [5 6 7]
copy(s[4:], s1)之后s1: [6 7 7]
n: 3  s: [0 1 2 3 5 6 7 7 8 9]
n: 6  s2: [0 1 2 3 5 6]
  • s1 之操作前为 [5 6 7]
  • copy(s[4:], s1) 翻译为 s 的 4 之后用 5, 6, 7 代替
    • s 变为 [0 1 2 3 5 6 7 7 8 9]
    • 由于 s1 取自于 s,所以变为[6, 7, 7]
  • copy(s2, s) 翻译为 s2 从头开始由 s 对应位置填充

5.4 字典

无序键值对集合,引用类型,使用 make 或 初始化语句 创建,内部使用散列表Hash实现

使用

定义
make(map[KeyType]ValueType, [cap])

cap是map的容量,非必须,但是最好指定

基本使用
func main() {
   scoreMap := make(map[string]int, 8)
   scoreMap["张三"] = 90
   scoreMap["小明"] = 100
   fmt.Println(scoreMap) // map[张三:90 小明:100]
   fmt.Println(scoreMap["小明"]) // 100
   fmt.Println(scoreMap["小红"]) // 0 默认值为0
   fmt.Printf("type of a: %T\n", scoreMap) // type of a: map[string]int
}

也可以在声明的时候填充元素

func main() {
   userInfo := map[string]string{
      "username": "admin",
      "password": "123",
   }
   fmt.Println(userInfo) // map[username:admin password:123]
}
判断某个键是否存在

v, ok := scoreMap["张三"]

  • 如果key存在ok为true, v为对应的值;

  • 不存在ok为false, v为值类型的零值

func main() {
   scoreMap := make(map[string]int)
   scoreMap["张三"] = 90
   scoreMap["小明"] = 100
   // 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值
   v, ok := scoreMap["张三"]
   if ok {
      fmt.Println(v) // 90
   } else {
      fmt.Println("查无此人")
   }
    
    v2, ok2 := scoreMap["张三2"]
	fmt.Println(ok2, v2) // false 0
}
遍历

使用 for range

func main() {
   scoreMap := make(map[string]int)
   scoreMap["张三"] = 90
   scoreMap["小明"] = 100
   scoreMap["小红"] = 90
   for k, v := range scoreMap {
      fmt.Println(k, v)
   }
}

也可以只遍历key

for k := range scoreMap {
    fmt.Println(k)
}
删除键值对
delete(scoreMap, "小明")

清空map的操作如下:

scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
scoreMap["小红"] = 90
scoreMap = make(map[string]int)
按照指定顺序遍历

第24行开始,根据key的顺序遍历map

func main() {
	rand.Seed(time.Now().UnixNano()) // 时间种子

	var scoreMap = make(map[string]int, 200)

	// 初始化 map
	// map[stu64:66 stu84:67 stu44:41 stu45:56 ... ]
	for i := 0; i < 100 ; i++ {
		key := fmt.Sprintf("stu%02d", i) // 生成 stu 开头的字符串
		value := rand.Intn(100) // 生成 0~99 的随机数
		scoreMap[key] = value
	}

	// 取出map中所有key存入切片keys
	// [stu64 stu84 stu44 stu45 ... ]
	var keys = make([]string, 0, 200)
	for key := range scoreMap {
		keys = append(keys, key)
	}

	// 排序切片
	sort.Strings(keys)
	// 按照排序后的 key 遍历 map
	for x, key := range keys {
		fmt.Println(x, key, scoreMap[key])
		// 0 stu00 61
		// 1 stu01 48
		// 2 stu02 13
		// ... 
	}
}
元素为 map类型 的切片
func main() {
   var mapSlice = make([]map[string]string, 3)

   for index, value := range mapSlice {
      fmt.Printf("index: %d, value: %v\n", index, value)
   }
   
   fmt.Println("after init")
   // [map[] map[] map[]] 外层是切片,内层是 map
    
   // 下面第一行必加,因为要定义切片内 map 的类型
   mapSlice[0] = make(map[string]string, 10)
   mapSlice[0]["name"] = "小王子"
   mapSlice[0]["password"] = "123"
   mapSlice[0]["address"] = "CN"
   fmt.Println(mapSlice) 
   // [map[name:小王子 password:123 address:CN] map[] map[]]

   for index, value := range mapSlice {
      fmt.Printf("index: %d, value: %v\n", index, value)
   }
   // index: 0, value: map[password:123 name:小王子 address:CN]
   // index: 1, value: map[]
   // index: 2, value: map[]
}

值为切片的map

func main() {
	// key 为 string,值value 为string类型的切片
	var sliceMap = make(map[string] []string, 3)
	fmt.Println(sliceMap)
	fmt.Println("after init")
  
	key := "中国"
	value, ok := sliceMap[key]
	if !ok {
		value = make([]string, 0, 2)
	}
	value = append(value, "北京", "上海")
	sliceMap[key] = value
	fmt.Println(sliceMap)
}
// map[]
// after init
// map[中国:[北京 上海]]

5.5 结构体

type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
    
}
  • 类型名:标识自定义结构体的名称,在同一个包内不能重复。
  • 字段名:表示结构体字段名。结构体中的字段名必须唯一。
  • 字段类型:表示结构体字段的具体类型。

实例化结构体

type person struct {
	name string
	city string
	age  int8
}

func main(){
    var p1 person
    p1.name = "Aris"
    fmt.Printf("p1=%#v\n", p1)
}
匿名结构体

临时数据场景使用

func main() {
   var user struct{Name string; Age int}

   user.Name = "Aris"
   user.Age = 21
   fmt.Printf("%#v\n", user)
}
指针类型结构体
  • 使用 new(person) 创建指针结构体
  • 使用 &person{}创建指针结构体
  • 支持结构体指针使用 . 访问结构体成员
func main() {
   var p = new(Person)
   p2 := &Person{}
   p.Name = "Aris"
   fmt.Printf("p=%#v\n", p) // p=&main.Person{Name:"Aris", Age:0}
}

结构体初始化

键值对

可以只初始化部分字段,其余字段为零值(string零值为""

p1 := Person{
   Name: "Aris",
   City: "AL",
   Age:  18,
}

p2 := &Person{
   Name: "Aris-2",
}
fmt.Printf("%#v", p2)
// &main.Person{Name:"Aris-2", City:"", Age:0}
只用值

需要 按顺序所有字段 进行初始化,不能和键值对方法混用

p3 := &Person{
   "Aris-3",
   "BJ",
   0,
}

==结构体内存布局==

结构体占用一块连续的内存

在 Go 中恰到好处的内存对齐


分享: