1-Go强化(1):指针、切片、map

一、指针加强

1 - 指针地址变量空间

  • 指针:指针就是地址; 指针变量就是存储地址的变量
    • *p:解引用、间接引用
func main()  {
	var a int = 10
	var p *int = &a
	a = 100
	fmt.Println("a = ", a)

	*p = 250  // 借助a 变量的地址,操作a对应空间
	fmt.Println("a = ", a)
	fmt.Println("*p = ", *p)
	
	a = 1000
	fmt.Println("*p = ", *p)
}

2 - 栈帧的内存布局

  • 栈帧概念:用来给函数运行提供内存空间,取内存于stack上
    • 当函数调用时,产生栈帧
    • 函数调用结束,释放栈帧
  • 栈帧存储的对象
    • 局部变量
    • 形参(形参与局部变量存储地位等同)
    • 内存字段描述值(栈顶指针值与栈基指针值)
      在这里插入图片描述

3 - 空指针和野指针

  • Go语言中的指针:Go语言保留了指针,但与C语言指针有所不同
    • 默认值 nil
    • 操作符 “&” 取变量地址, “*” 通过指针访问目标对象
    • 不支持指针运算,不支持 “->” 运算符,直接 “.” 访问目标成员
  • 空指针:未被初始化的指针var p *int *p --> err
  • 野指针:被一片无效的地址空间初始化

4 - 函数new

  • new函数
    • 表达式new(T)将创建一个T类型的匿名变量,所做的是为T类型的新值分配并清零一块内存空间,然后将这块内存空间的地址作为结果返回,而这个结果就是指向这个新的T类型值的指针值,返回的指针类型为*T
    • new创建的内存空间位于heap上,空间的默认值为数据类型默认值。如:new(int) 则 *p为0,new(bool) 则 *p为false
    • 我们只需使用new()函数,无需担心其内存的生命周期或怎样将其删除,因为Go语言的内存管理系统会帮我们打理一切(虽然gc会帮我们回收,我们使用完后强烈建议置成nil,以提示gc进行回收)
func main()  {
	var p *int
	p = new (int) // 对应类型的默认值
	fmt.Println(*p) // 0
}

func main() {
	var p *bool
	p = new(bool) // 对应类型的默认值
	fmt.Println(*p) //false
}

5 - 左值与右值

  • 左值:等号 左边的变量,代表 变量所指向的内存空间。 (写)
  • 右值:等号 右边的变量,代表 变量内存空间存储的数据值。 (读)
var a int = 10
var b int = 20 // b是左值
a = b // b是右值

6 - 指针的函数传参

  • 函数传参方式函数传参永远是值传递(传引用传递的是地址的值)
    • 传地址(引用):将形参的地址值作为函数参数传递
    • 传值(数据据):将实参的 值 拷贝一份给形参
  • 指针的函数传参
    • 传引用:在A栈帧内部,修改B栈帧中的变量值
func swap(a, b int) {
	a, b = b, a
	fmt.Println("swap  a:", a, "b:", b) // swap  a: 20 b: 10
}

func swap2(x, y *int) {
	*x, *y = *y, *x
}

func main() {
	a, b := 10, 20
	swap(a, b)
	fmt.Println("swap: main  a:", a, "b:", b)  // swap: main  a: 10 b: 20
	swap2(&a, &b)                              // 传地址值
	fmt.Println("swap2: main  a:", a, "b:", b) // swap2: main  a: 20 b: 10
}

二、切片加强

1 - 切片的基础与本质

  • 为什么用切片:在Go语言当,我们几乎可以在所有的场景中,使用切片替换数组使用
    • 数组的容量固定,不能自动拓展
    • 值传递: 数组作为函数参数时,将整个数组值拷贝一份给形参
  • 切片的本质
    • 不是一个数组的指针,是一种数据结构体,用来操作数组内部元素
    • 切片并不是数组或数组指针,它通过内部指针和相关属性引用数组片段,以实现变长案
  • 数组和切片定义的区别
    • 创建数组时 ,[n]内指定数组长度
    • 创建切片时,[]内为空,或者 …(很少使用…)

2 - 切片截取、创建、append和copy

  • 切片截取切片名称 [ low : high : max ](截取数组,初始化 切片时,没有指定切片容量时, 切片容量跟随原数组(切片))
    • low:起始下标位置
    • high:结束下标位置 len = high - low
    • 容量:cap = max - low
func main() {
	arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

	s := arr[1:3:5]
	fmt.Println("s = ", s)
	fmt.Println("len(s) = ", len(s))
	fmt.Println("cap(s) = ", cap(s))

	s1 := arr[1:5:7]
	fmt.Println("s1 = ", s1)
	fmt.Println("len(s1) = ", len(s1)) // 5-1 == 4
	fmt.Println("cap(s1) = ", cap(s1)) // 7-1

	s2 := s1[0:6]
	fmt.Println("s = ", s2)
	fmt.Println("len(s) = ", len(s2)) // 6-0 == 6
	fmt.Println("cap(s) = ", cap(s2)) // 跟随s1的容量6
}

注意:make只能创建slice、map和channel,并且返回一个有初始值(非零)的对象

  • 切片创建方式
    • 自动推导类型创建切片:slice := []int {1, 2, 4, 6}
    • slice := make([]int, 长度,容量)
    • slice := make([]int, 长度):创建切片时,没有指定容量,容量== 长度【常用】
func main() {
	// 1. 自动推导赋初值
	s1 := []int{1, 2, 4, 6}
	fmt.Println("s1 = ", s1)

	s2 := make([]int, 5, 10)
	fmt.Println("len=", len(s2), "cap=", cap(s2))

	s3 := make([]int, 7)
	fmt.Println("len=", len(s3), "cap=", cap(s3))
}
  • 切片做函数参数:引用传递(传地址)
  • append:在切片末尾追加元素
    • append(切片对象, 待追加元素)
    • append函数会智能的将底层数组的容量增长,一旦超过原底层数组容量,通常以2倍(1024以下)容量重新分配底层数组,并复制原来的数据
    • 因此,使用append 给切片做扩充时,切片的地址可能发生变化。但数据都被重新保存了,不影响使用
func main() {
	s1 := []int{1, 2, 4, 6} // 创建一个有初始值的切片

	s1 = append(s1, 888)
	s1 = append(s1, 888)
	s1 = append(s1, 888)
	s1 = append(s1, 888)
	s1 = append(s1, 888)

	fmt.Println("s1=", s1)
}
  • copy:函数 copy 在两个 slice 间复制数据,复制长度以 len 小的为准,两个 slice 指向同一底层数组。直接对应位置覆盖
func main() {
	data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	s1 := data[8:]  // {8, 9}
	s2 := data[0:5] // {0, 1, 2 3 4}

	copy(s2, s1)
	fmt.Println("s2=", s2) // s2= [8 9 2 3 4]
}

3 - 切片案例

  • 案例1:给定一个字符串列表,在原有slice上返回不包含空字符串的列表
package main

import "fmt"

func noEmpty(data []string) []string {
	out := data[:0] // 在原切片上截取一个长度为 0 的切片 == make([]string, 0)
	for _, str := range data {
		if str != "" {
			out = append(out, str)
		}
		// 取到空字符串,不作为
	}
	return out
}

// 直接在原串上操作(不适用append实现)
func noEmpty2(data []string) []string {
	i := 0
	for _, str := range data {
		if str != "" {
			data[i] = str
			i++
		}
		// 取到空字符串,不作为
	}
	return data[:i]
}

func main() {
	data := []string{"red", "", "black", "", "", "pink", "blue"}
	afterData := noEmpty2(data)
	fmt.Println("afterData:", afterData)
}
  • 案例2:写一个函数,消除[]string中重复字符串
package main

import "fmt"

func noSame(data []string) []string {
	out := data[:1]
	// 遍历原始切片字符串
	for _, word := range data {
		i := 0
		// 比较取出的 word 是否在 out 中存在 -- for
		for ; i < len(out); i++ {
			if word == out[i] {
				break
			}
		}
		if i == len(out) { //这个可以保证没有触发break的时候才执行append
			out = append(out, word)
		}
	}
	return out
}

func main() {
	data := []string{"red", "black", "red", "yellow", "yellow", "pink", "blue", "pink", "blue"}
	afterData := noSame(data)
	fmt.Println("Afterdata:", afterData)
}
  • 案例3:要删除slice中间的某个元素并保存原有的元素顺序
func remove(data []int, idx int) []int {
	// {5, 6, 8, 9, 9}:将 8,9拷贝到7,8,然后丢弃掉最后一个9
	copy(data[idx:], data[idx+1:])
	return data[:len(data)-1]
}

func main() {
	data := []int{5, 6, 7, 8, 9}
	afterData := remove(data, 2)
	fmt.Println("afterData:", afterData) // afterData: [5 6 8 9]
}

三、map加强

1 - map创建与初始化

  • map简述:Go语言中的map(映射、字典)是一种内置的数据结构,它是一个无序的key-value对的集合,比如以身份证号作为唯一键来标识一个人的信息。Go语言中并没有提供一个set类型,但是map中的key也是不相同的,可以用map实现类似set的功能
    • 在一个map里所有的键都是唯一的,而且必须是支持==和!=操作符的类型,切片、函数以及包含切片的结构类型这些类型由于具有引用语义,不能作为映射的键,使用这些类型会造成编译错误
  • map创建方式
    • var m1 map[int]string:不能存储数据
    • m2 := map[int]string{}:能存储数据
    • m3 := make(map[int]string):默认len = 0
    • m4 := make(map[int]string, 10)
func main() {
	var m1 map[int]string // 声明map ,没有空间,不能直接存储key -- value
	//m1[100] = "Green"
	if m1 == nil {
		fmt.Println("map is nil ")
	}

	m2 := map[int]string{}
	fmt.Println(len(m2))
	fmt.Println("m2 = ", m2)
	m2[4] = "red"
	fmt.Println("m2 = ", m2)

	m3 := make(map[int]string)
	fmt.Println(len(m3))
	fmt.Println("m3 = ", m3)
	m3[400] = "red"
	fmt.Println("m3 = ", m3)

	m4 := make(map[int]string, 5) // len
	fmt.Println("len(m4) = ", len(m4))
	//fmt.Println("len(m4) = ", cap(m4))		// 不能在map中使用 cap()
	fmt.Println("m4 = ", m4)
}
  • map初始化
    • var m map[int]string = map[int]string{ 1: "aaa", 2:"bbb"}:保证key彼此不重复
    • m := map[int]string{ 1: "aaa", 2:"bbb"}
func main() {
	// 初始化map
	var m5 map[int]string = map[int]string{1: "Luffy", 130: "Sanji", 1301: "Zoro"}
	fmt.Println("m5 = ", m5)
	m6 := map[int]string{1: "Luffy", 130: "Sanji", 1303: "Zoro"}
	fmt.Println("m6 = ", m6)
}

2 - map赋值

  • map赋值特点
    • 赋值过程中,如果新map元素的key与原map元素key 相同 ——> 覆盖(替换)
    • 赋值过程中,如果新map元素的key与原map元素key 不同 ——> 添加
func main() {
	m7 := make(map[int]string, 1)
	m7[100] = "Nami"
	m7[20] = "Hello"
	m7[3] = "world"
	fmt.Println("m7=", m7)

	m7[3] = "yellow" // 成功! 将原map中 key 值为 3 的map元素,替换
	fmt.Println("m7=", m7)
}

3 - map的使用

  • map遍历
    • for key值, value值 := range map
    • for key值 := range map
func main() {
	// 遍历map
	var m8 map[int]string = map[int]string{1: "Luffy", 130: "Sanji", 1301: "Zoro"}
	for k, v := range m8 {
		fmt.Printf("key:%d --- value:%q\n", k, v)
	}

	// range返回的key/value;省略value打印
	for K := range m8 {
		fmt.Printf("key:%s\n", K)
	}
}
  • 判断map中key是否存在
    • map[下标] 运算:返回两个值
      • 第一个表 value 的值,如果value不存在。 nil
      • 第二个表 key是否存在的bool类型。存在 true, 不存在false
func main() {
	// 判断 map 中的key 是否存在
	var m9 map[int]string = map[int]string{1: "Luffy", 130: "Sanji", 1301: "Zoro"}

	if v, has := m9[12]; has { // m9[下标] 返回两个值,第一个是value,第二个是bool 代表key是否存在
		fmt.Println("value=", v, "has=", has)
	} else {
		fmt.Println("false value=", v, "has=", has)
	}
}

4 - map的删除与传参

  • map删除元素delete(map, key)
    • 参1:待删除元素的map;参2:key值
    • 删除一个不存在的key,不会报错
  • map 做函数参数和返回值:传递的是引用
// map做函数参数、返回值,传引用
func mapDelete(m map[int]string, key int) {
	delete(m, key) // 删除 m 中 键值为 key的 map 元素
}

func main() {
	m := map[int]string{1: "Luffy", 130: "Sanji", 1301: "Zoro"}
	fmt.Println("before delete m :", m)
	mapDelete(m, 130)
	fmt.Println("after delete m :", m)
}
  • 案例:封装 wcFunc() 函数。接收一段英文字符串str。返回一个map,记录str中每个“词”出现次数的
package main

import (
	"fmt"
	"strings"
)

func wordCountFunc(str string) map[string]int {
	s := strings.Fields(str)  // 将字符串,拆分成字符串切片s
	m := make(map[string]int) // 创建一个用于存储 word 出现次数的 map

	// 遍历拆分后的字符串切片
	for i := 0; i < len(s); i++ {
		if _, ok := m[s[i]]; ok { // ok == ture 说明 s[i] 这个key存在
			m[s[i]] = m[s[i]] + 1 // m[s[i]]++
		} else { // 说明 s[i] 这个key不存在, 第一次出现。添加到map中
			m[s[i]] = 1
		}
	}
	return m
}

func wordCountFunc2(str string) (m map[string]int) {
	m = make(map[string]int)
	arr := strings.Fields(str)
	for _, v := range arr {
		m[v]++
	}
	return
}

func main() {
	str := "I love my work and I I I I love love love my family too"
	//mRet := wordCountFunc(str)
	mRet := wordCountFunc2(str)

	// 遍历map ,展示每个word 出现的次数:
	for k, v := range mRet {
		fmt.Printf("%q:%d\n", k, v)
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无休止符

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值