day06 数据类型:指针、切片、字典

news/2024/9/19 2:33:55/文章来源:https://www.cnblogs.com/fuminer/p/18416927

day06 数据类型

Go语言中常见的数据类型有很多,例如:

  • 整型,用于表示整数。
  • 浮点型,用于表示小数。
  • 布尔型,用于表示真/假。
  • 字符串,用于表示文本信息。
  • 数组,用于表示多个数据(数据集合)
  • 指针,用于表示内存地址的类型。
  • 切片,用于表示多个数据(数据集合)
  • 字典,用于表示键值对结合。
  • 结构体,用于自定义一些数据集合。
  • 接口,用于约束和泛指数据类型。

今日概要

  • 切片,用于表示多个数据(数据集合),可以理解为动态数组。 [alex,18,3]
  • 字典,键值对。例如:
  • 指针,用于表示内存地址的类型。

1. 切片

切片,动态数组。

切片是Go中重要的数据类型,每个切片对象内部都维护着:数组指针、切片长度、切片容量 三个数据。

type slice struct {array unsafe.Pointerlen   intcap   int
}

image-20200617213213399

在向切片中追加的数据个数大于容量时,内部会自动扩容且每次扩容都当前容量的2倍(当容量超过1024时每次扩容则只增加 1/4容量)。

1.1 创建切片

// 创建切片
var nums []int// 创建切片
var data = []int{11,22,33}
data:= []int{11,22,33}// 创建切片
// make只用于 切片、字典、channel
var users = make([]int,1,3)
// 切片的指针类型
var v1 = new([]int)// 指针类型(nil)
var v2 *[]int

1.2 自动扩容

v1 := make([]int,1,3)fmt.Println(len(v1),cap(v1))// 其他
data := make([]int,3)
image-20200620113636908
v1 := make([]int,1,3)v2 := append(v1,66)fmt.Println(v1)  // [0 ]
fmt.Println(v2)  // [0,66]v1[0] = 999
fmt.Println(v1)  // [999 ]
fmt.Println(v2)  // [999,66]// 需求:有一个切片,请往一个切片中追加一个数据。
v3 := make([]int,1,3)
v3 = append(v3,999)
image-20200620114104494
v1 := []int{11,22,33}
v2 := append(v1,44)v1[0] = 999fmt.Println(v1) // [999 22 33]
fmt.Println(v2) // [11 22 33 44]

image-20200620114904089

1.3 常见操作

1.3.1 长度和容量

v1 := []int{11,22,33}
fmt.Println(len(v1), cap(v1))

1.3.2 索引

v1 := []string{"alex","李杰","老男孩"}
v1[0]
v1[1]
v1[2]v2 := make([]int,2,5)
v2[0]
v2[1]
v2[2]  // 报错v2[0] = 999

1.3.3 切片

v1 := []int{11,22,33,44,55,66}v2 := v1[1:3]
v3 := v1[1:]
v4 := v1[:3]// 注意:通过切片切出来的数据和原切片内部存储的数据地址相同

1.3.4 追加

/*@Author:武沛齐  微信号:wupeiqi666@Description: 老男孩IT教育 & 路飞学城@Video:  https://space.bilibili.com/283478842
*/
package mainimport ("fmt"
)func main() {v1 := []int{11,22,33}v2 := append(v1,44)v3 := append(v1,55,66,77,88)v4 := append(v1, []int{100,200,300}...)fmt.Println(v2)fmt.Println(v3)fmt.Println(v4)
}>>> 输出
[11 22 33 44]
[11 22 33 55 66 77 88]
[11 22 33 100 200 300]

1.3.5 删除

/*@Author:武沛齐  微信号:wupeiqi666@Description: 老男孩IT教育 & 路飞学城@Video:  https://space.bilibili.com/283478842
*/
package mainimport "fmt"func main() {v1 := []int{11, 22, 33, 44, 55, 66}deleteIndex := 2// 切片获取到 {11,22,44、55、66}// 又获取到 {44, 55, 66},将44、55、66要追加到result := append(v1[:deleteIndex], v1[deleteIndex+1:]...)fmt.Println(result) // [11 22 44 55 66]fmt.Println(v1)     //[11 22 44 55 66 66]
}

注意:使用切片时不太会使用删除。【链表】

1.3.6 插入

/*@Author:武沛齐  微信号:wupeiqi666@Description: 老男孩IT教育 & 路飞学城@Video:  https://space.bilibili.com/283478842
*/
package mainimport "fmt"func main() {v1 := []int{11, 22, 33, 44, 55, 66}insertIndex := 3 // 在索引3的位置插入99result := make([]int, 0, len(v1)+1)result = append(result, v1[:insertIndex]...)result = append(result,99)result = append(result,v1[insertIndex:]...)fmt.Println(result)
}

注意:效率低下。【链表】

1.3.7 循环

/*@Author:武沛齐  微信号:wupeiqi666@Description: 老男孩IT教育 & 路飞学城@Video:  https://space.bilibili.com/283478842
*/
package mainimport "fmt"func main() {v1 := []int{11, 22, 33, 99, 55, 66}for i := 0; i < len(v1); i++ {fmt.Println(i, v1[i])}for index, value := range v1 {fmt.Println(index, value)}
}

1.4 切片嵌套

/*@Author:武沛齐  微信号:wupeiqi666@Description: 老男孩IT教育 & 路飞学城@Video:  https://space.bilibili.com/283478842
*/
package mainimport "fmt"func main() {v1 := []int{11, 22, 33, 99, 55, 66}v2 := [][]int{[]int{11, 22, 33, 44}, []int{44, 55}}v3 := [][2]int{[2]int{1, 2}, [2]int{4, 5}}fmt.Println(v1)fmt.Println(v2)fmt.Println(v3)v1[0] = 111111v2[0][2] = 222222v3[1][0] = 99999fmt.Println(v1)fmt.Println(v2)fmt.Println(v3)
}

1.5 变量赋值

  • 整型

    /*@Author:武沛齐  微信号:wupeiqi666@Description: 老男孩IT教育 & 路飞学城@Video:  https://space.bilibili.com/283478842
    */
    package mainimport "fmt"func main() {v1 := 1v2 := v1fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc0000b4008fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc0000b4010
    }
    
  • 布尔类型

    /*@Author:武沛齐  微信号:wupeiqi666@Description: 老男孩IT教育 & 路飞学城@Video:  https://space.bilibili.com/283478842
    */
    package mainimport "fmt"func main() {v1 := falsev2 := v1fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc00012a002fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc00012a003
    }
    
  • 浮点型

    /*@Author:武沛齐  微信号:wupeiqi666@Description: 老男孩IT教育 & 路飞学城@Video:  https://space.bilibili.com/283478842
    */
    package mainimport "fmt"func main() {v1 := 3.14v2 := v1fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc000016050fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc000016058
    }
  • 字符串

    /*@Author:武沛齐  微信号:wupeiqi666@Description: 老男孩IT教育 & 路飞学城@Video:  https://space.bilibili.com/283478842
    */
    package mainimport "fmt"func main() {v1 := "武沛齐"v2 := v1fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc000010200fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc000010210 
    }
    

    image-20200620234120353
    注意:字符串内部元素不可被修改。

  • 数组

    /*@Author:武沛齐  微信号:wupeiqi666@Description: 老男孩IT教育 & 路飞学城@Video:  https://space.bilibili.com/283478842
    */
    package mainimport "fmt"func main() {v1 := [2]int{6, 9}v2 := v1fmt.Println(v1, v2)fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc0000b4010fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc0000b4020v1[0] = 11111fmt.Println(v1, v2)
    }
  • 切片

    /*@Author:武沛齐  微信号:wupeiqi666@Description: 老男孩IT教育 & 路飞学城@Video:  https://space.bilibili.com/283478842
    */
    package mainimport "fmt"func main() {v1 := []int{6, 9}v2 := v1fmt.Println(v1, v2)              // [6 9] [6 9]fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc0000a6020fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc0000a6040v1[0] = 11111fmt.Println(v1, v2) // [11111 9] [11111 9]
    }

    image-20200620234833936
    如果扩容,那么内部存储数据的数组就会重新开辟区域。

    package mainimport "fmt"func main() {v1 := []int{6, 9}v2 := v1fmt.Println(v1, v2)              // [6 9] [6 9]fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc0000a6020fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc0000a6040v1 = append(v1, 999)fmt.Println(v1, v2) // [11111 9] [11111 9]
    }

总结,目前所学的所有的数据类型中,在修改切片的内部元素时,会造成所有的赋值的变量同时修改(不扩容)。

扩展:引用类型和值类型

1.6 练习题

  1. 简述切片和数组的区别?

  2. 简述new和make的区别?

  3. 看代码写结果

    v1 := make([]int,2,5)
    fmt.Println(v1[0],len(v1),cap(v1))
    
  4. 看代码写结果

    v1 := make([]int,2,5)
    v2 := append(v1,123)fmt.Println(len(v1),cap(v1)) // 2,5
    fmt.Println(len(v2),cap(v2)) // 3,5
    fmt.Println(v1) // [0 0]
    fmt.Println(v2) // [0 0 123]
    

    image-20200621002506511

  5. 看代码写结果

    v1 := make([]int,2,5)
    v2 := append(v1,123)
    v1[0] = 999fmt.Println(v1) // [999 0]
    fmt.Println(v2) // [999, 0, 123]
    
  6. 看代码写结果

    v1 := make([]int,2,2)
    v2 := append(v1,123)
    v1[0] = 999fmt.Println(v1) // [999 0]
    fmt.Println(v2) // [0 0 123]
    

    image-20200621002905735

  7. 看代码写结果

    v1 := make([]int,2,2)
    v2 := v1[0:2]v1[0] = 111fmt.Println(v1) // [111,0]
    fmt.Println(v2) // [111,0]
    
  8. 看代码写结果

    v1 := [][]int{ []int{11, 22, 33, 44},  []int{44, 55} }
    v1[0][2] = 999
    fmt.Println(v1) // [ [11 22 99 44]  [44,55] ]
    
  9. 看代码写结果

    v1 := [][]int{[]int{11, 22, 33, 44}, []int{44, 55}}
    v2 := v1[0]v2[2] = 69
    fmt.Println(v1)  // [[11 22 69 44] [44 55]]
    
  10. 看代码写结果

    v1 := [][]int{[]int{11, 22, 33, 44}, []int{44, 55}}
    v2 := append(v1[0],99)v2[2] = 69
    fmt.Println(v1) // [[11 22 33 44] [44 55]]
    
  11. 看代码写结果

    v1 := [][]int{ make([]int, 2, 5), make([]int, 2, 3) }
    v2 := append(v1[0], 99)fmt.Println(v1) // [ [0 0], [0 0 ] ]
    fmt.Println(v2) // [0 0 99]v2[0] = 69
    fmt.Println(v1) // [ [69 0], [0 0 ] ]
    fmt.Println(v2) // [69 0 99]
    

2.字典类型(Map)

在学习任何的编程语言时,一般都会一种数据类型称为:字典(dict)或映射(map),以键值对为元素的数据集合。例如:

{"age":"18","name":"武沛齐","email":"wupeiqi@live.com"
}

这种类型最大的特点就是查找速度非常快,因为他的底层存储是基于哈希表存储的(不同语言还会有一些差异)。

取模+拉链法来快速了解下哈希表存储原理,如下图:

image-20201006100506181

这种结构之所以快,是因为根据key可以直接找到数据存放的位置;而其他的数据类型是需要从前到后去逐一比对,相对来说比较耗时。

以上只是基本的存储模型,而各个编程语言中的字典都会在此基础上进行相应的修改和优化(后续会深入讲解Golang中的map实现机制)。

Map的特点:

  • 键不能重复
  • 键必须可哈希(目前我们已学的数据类型中,可哈希的有:int/bool/float/string/array)
  • 无序

接下来关于map我会从两个维度来进行讲解:

  • 常见使用
  • 底层原理剖析(面试常问)

2.1 声明&初始化

// userInfo := map[string]string{}
userInfo := map[string]string{"name":"武沛齐","age":"18"}userInfo["name"]  // 武沛齐
userInfo["age"] = "20"
userInfo["email"] = "wupeiqi@live.com"
// data := make(map[int]int, 10)
data := make(map[int]int)
data[100] = 998
data[200] = 999
data := make(map[string]int)
data["100"] = 998
data["200"] = 999// 声明,nil
var row map[string]int
row = data
data := make(map[string]int)
data["100"] = 998
data["200"] = 999// 声明,nil
value := new(map[string]int)
// value["k1"] = 123  # 报错
value = &data

注意:键不重复 & 键必须可哈希(int/bool/float/string/array)

v1 := make(map[[2]int]float32)
v1[[2]int{1,1}] = 1.6
v1[[2]int{1,2}] = 3.4v2 := make(map[[2]int][3]string )
v2[[2]int{1,1}] = [3]string{"武沛齐","alex","老妖"}

2.2 常用操作

2.2.1 长度和容量

data := map[string]string{"n1":"武沛齐","n2":"alex"}
value := len(data)  // 2
// 根据参数值(10),计算出合适的容量。
// 一个map 中会包含很多桶,每个桶中可以存放8个键值对。
info := make(map[string]string, 10)info["n1"] = "武沛齐"
info["n2"] = "alex"v1 := len(info)  // 2
// v2 := cap(info)  // 报错

2.2.2 添加

data := map[string]string{"n1":"武沛齐","n2":"alex"}
data["n3"] = "eric"

2.2.3 修改

data := map[string]string{"n1":"武沛齐","n2":"alex"}
data["n1"] = "eric"

2..2.4 删除

data := map[string]string{"n1":"武沛齐","n2":"alex"}
delete(data,"n2")

2.2.5 查看

data := map[string]string{"n1":"武沛齐","n2":"alex"}
data["n1"]
data := map[string]string{"n1":"武沛齐","n2":"alex"}
for key,value := range data{fmt.Println(key,value)
}
data := map[string]string{"n1":"武沛齐","n2":"alex"}
for key := range data{fmt.Println(key) 
}
data := map[string]string{"n1": "武沛齐", "n2": "alex"}
for _, value := range data {fmt.Println(value)
}

2.2.6 嵌套

v1 := make(map[string]int)
v2 := make(map[string]string)
v3 := make(map[string]...)
v4 := make(map[string][2]int)
v5 := make(map[string][]int)
v6 := make(map[string]map[int]int)
v7 := make(map[string][2]map[string]string)
v7["n1"] = [2]map[string]string{ map[string]string{"name":"武沛齐","age":"18"},map[string]string{"name":"alex","age":"78"}}
v7["n2"] = [2]map[string]string{ map[string]string{"name":"eric","age":"18"},map[string]string{"name":"seven","age":"78"}}// 伪代码
v7 = {n1:[{"name":"武沛齐","age":"18"},{"name":"alex","age":"78"}],n2:[{"name":"eric","age":"18"},{"name":"seven","age":"78"}]
}

前提:键不重复 & 键必须可哈希

v8 := make(map[int]int)
v9 := make(map[string]int)
v10 := make(map[float32]int)
v11 := make(map[bool]int)
v12 := make(map[ [2]int ]int)
v13 := make(map[ []int ]int) // 错误,不可哈希
v14 := make(map[ map[int]int ]int) // 错误,不可哈希
v15 := make(map[ [2][]int ]int) // 报错
v16 := make(map[ [2]map[string]string ]int) // 报错

2.2.7 变量赋值

v1 := map[string]string{"n1":"武沛齐","n2":"alex"}
v2 := v1v1["n1"] = "wupeiqi"ftm.Println(v1) // {"n1":"wupeiqi","n2":"alex"}
ftm.Println(v2) // {"n1":"wupeiqi","n2":"alex"}

特别提醒:无论是否存在扩容都指向同一个地址。

2.3 Map底层原理剖析

Golang中的Map有自己的一套实现原理,其核心是由hmapbmap两个结构体实现。

image-20200919191800912

2.3.1 初始化

// 初始化一个可容纳10个元素的map
info = make(map[string]string,10)
  • 第一步:创建一个hmap结构体对象。

  • 第二步:生成一个哈希因子hash0 并赋值到hmap对象中(用于后续为key创建哈希值)。

  • 第三步:根据hint=10,并根据算法规则来创建 B,当前B应该为1。

    hint            B
    0~8				0
    9~13            1
    14~26           2
    ...
    
  • 第四步:根据B去创建去创建桶(bmap对象)并存放在buckets数组中,当前bmap的数量应为2.

    • 当B<4时,根据B创建桶的个数的规则为:$2^B$(标准桶)
    • 当B>=4时,根据B创建桶的个数的规则为:$2^B$ + $2^{B-4}$(标准桶+溢出桶)

    注意:每个bmap中可以存储8个键值对,当不够存储时需要使用溢出桶,并将当前bmap中的overflow字段指向溢出桶的位置。

image-20200919191800912

2.3.2 写入数据

info["name"] = "武沛齐"

在map中写入数据时,内部的执行流程为:

  • 第一步:结合哈希因子和键 name生成哈希值 011011100011111110111011011

  • 第二步:获取哈希值的后B位,并根据后B为的值来决定将此键值对存放到那个桶中(bmap)。

    将哈希值和桶掩码(B个为1的二进制)进行 & 运算,最终得到哈希值的后B位的值。假设当B为1时,其结果为 0 :
    哈希值:011011100011111110111011010
    桶掩码:000000000000000000000000001
    结果:  000000000000000000000000000 = 0通过示例你会发现,找桶的原则实际上是根据后B为的位运算计算出 索引位置,然后再去buckets数组中根据索引找到目标桶(bmap)。
    
  • 第三步:在上一步确定桶之后,接下来就在桶中写入数据。

    获取哈希值的tophash(即:哈希值的`高8位`),将tophash、key、value分别写入到桶中的三个数组中。
    如果桶已满,则通过overflow找到溢出桶,并在溢出桶中继续写入。注意:以后在桶中查找数据时,会基于tophash来找(tophash相同则再去比较key)。
    
  • 第四步:hmap的个数count++(map中的元素个数+1)

image-20200919191800912

2.3.3 读取数据

value := info["name"]

在map中读取数据时,内部的执行流程为:

  • 第一步:结合哈希引子和键 name生成哈希值。

  • 第二步:获取哈希值的后B位,并根据后B为的值来决定将此键值对存放到那个桶中(bmap)。

  • 第三步:确定桶之后,再根据key的哈希值计算出tophash(高8位),根据tophash和key去桶中查找数据。

    当前桶如果没找到,则根据overflow再去溢出桶中找,均未找到则表示key不存在。
    

image-20200919191800912

2.3.4 扩容

在向map中添加数据时,当达到某个条件,则会引发字典扩容。

扩容条件:

  • map中数据总个数 / 桶个数 > 6.5 ,引发翻倍扩容。
  • 使用了太多的溢出桶时(溢出桶使用的太多会导致map处理速度降低)。
    • B <=15,已使用的溢出桶个数 >= $2^B$ 时,引发等量扩容。
    • B > 15,已使用的溢出桶个数 >= $2^{15}$ 时,引发等量扩容。
func hashGrow(t *maptype, h *hmap) {// If we've hit the load factor, get bigger.// Otherwise, there are too many overflow buckets,// so keep the same number of buckets and "grow" laterally.bigger := uint8(1)if !overLoadFactor(h.count+1, h.B) {bigger = 0h.flags |= sameSizeGrow}oldbuckets := h.bucketsnewbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil)...
}

当扩容之后:

  • 第一步:B会根据扩容后新桶的个数进行增加(翻倍扩容新B=旧B+1,等量扩容 新B=旧B)。
  • 第二步:oldbuckets指向原来的桶(旧桶)。
  • 第三步:buckets指向新创建的桶(新桶中暂时还没有数据)。
  • 第四步:nevacuate设置为0,表示如果数据迁移的话,应该从原桶(旧桶)中的第0个位置开始迁移。
  • 第五步:noverflow设置为0,扩容后新桶中已使用的溢出桶为0。
  • 第六步:extra.oldoverflow设置为原桶(旧桶)已使用的所有溢出桶。即:h.extra.oldoverflow = h.extra.overflow
  • 第七步:extra.overflow设置为nil,因为新桶中还未使用溢出桶。
  • 第八步:extra.nextOverflow设置为新创建的桶中的第一个溢出桶的位置。

image-20200920154253729

2.3.5 迁移

扩容之后,必然要伴随着数据的迁移,即:将旧桶中的数据要迁移到新桶中。

翻倍扩容

如果是翻倍扩容,那么迁移规就是将旧桶中的数据分流至新的两个桶中(比例不定),并且桶编号的位置为:同编号位置 和 翻倍后对应编号位置。

image-20201006081842270

那么问题来了,如何实现的这种迁移呢?

首先,我们要知道如果翻倍扩容(数据总个数 / 桶个数 > 6.5),则新桶个数是旧桶的2倍,即:map中的B的值要+1(因为桶的个数等于$2B$,而翻倍之后新桶的个数就是$2B$ * 2 ,也就是$2^{B+1}$,所以 新桶的B的值=原桶B + 1 )。

迁移时会遍历某个旧桶中所有的key(包括溢出桶),并根据key重新生成哈希值,根据哈希值的 底B位 来决定将此键值对分流道那个新桶中。

image-20201006085640008

扩容后,B的值在原来的基础上已加1,也就意味着通过多1位来计算此键值对要分流到新桶位置,如上图:

  • 当新增的位(红色)的值为 0,则数据会迁移到与旧桶编号一致的位置。
  • 当新增的位(红色)的值为 1,则数据会迁移到翻倍后对应编号位置。

例如:

旧桶个数为32个,翻倍后新桶的个数为64。
在重新计算旧桶中的所有key哈希值时,红色位只能是0或1,所以桶中的所有数据的后B位只能是以下两种情况:- 000111【7】,意味着要迁移到与旧桶编号一致的位置。- 100111【39】,意味着会迁移到翻倍后对应编号位置。特别提醒:同一个桶中key的哈希值的低B位一定是相同的,不然不会放在同一个桶中,所以同一个桶中黄色标记的位都是相同的。
等量扩容

如果是等量扩容(溢出桶太多引发的扩容),那么数据迁移机制就会比较简单,就是将旧桶(含溢出桶)中的值迁移到新桶中。

这种扩容和迁移的意义在于:当溢出桶比较多而每个桶中的数据又不多时,可以通过等量扩容和迁移让数据更紧凑,从而减少溢出桶。

3.指针

指针,是一种数据类型,用于表示数据的内存地址。

// 声明一个 字符串类型 的变量(默认初始化值为空字符串)。
var v1 string// 声明一个 字符串的指针类型 的变量(默认初始化值为nil)。
var v2 *string var v3 intvar v4 *int
// 声明一个 字符串类型 的变量,值为 武沛齐。
var name string = "武沛齐"// 声明一个 字符串的指针类型 的变量,值为 name 对应的内存地址。
var pointer *string = &namevar age int = 18
var x1 *int = &age
image-20201030144654619

3.1 指针的意义

相当于创建了一个地址的引用,以后根据这个引用再去获取他里面的值。

image-20201030163932778

/*@Author:武沛齐  微信号:wupeiqi666@Description: 老男孩IT教育 & 路飞学城@Video:  https://space.bilibili.com/283478842
*/
package mainimport "fmt"func main() {v1 := "武沛齐"v2 := &v1fmt.Println(v1, v2, *v2) // 武沛齐 0xc00008e1e0 武沛齐v1 = "alex"fmt.Println(v1, v2, *v2) // alex 0xc00008e1e0 alex
}

3.2 指针的应用场景

场景1:

v1 := "武沛齐"
v2 := v1
v1 = "alex"fmt.Println(v1,v2) // alex 武沛齐
v1 := "武沛齐"
v2 := &v1
v1 = "alex"
fmt.Println(v1,*v2) // alex  alex
image-20201030170159212

场景2:

package mainimport "fmt"func changeData(data string) {data = "哈哈哈哈哈"
}func main() {name := "武沛齐"// 本质上会将name的值拷贝一份,并赋值给datachangeData(name)fmt.Println(name) // 武沛齐
}
package mainimport "fmt"func changeData(ptr *string) {*ptr = "哈哈哈哈哈"
}func main() {name := "武沛齐"changeData(&name)fmt.Println(name) // 哈哈哈哈哈
}

场景3:

package mainimport "fmt"func main() {var username stringfmt.Printf("请输入用户名:")// 提示用户输入,当用户输入之后,将输入的值赋值给内存地址对应的区域中。fmt.Scanf("%s", &username)if username == "武沛齐" {fmt.Println("登录成功")} else {fmt.Println("登录失败")}
}

3.3 指针的指针

name := "武沛齐"// 声明一个指针类型变量p1,内部存储name的内存地址
var p1 *string = &name// 声明一个指针的指针类型变量p2,内部存储指针p1的内存地址
var p2 **string = &p1// 声明一个指针的指针的指针类型变量p3,内部存储指针p2的内存地址
var p3 ***string = &p2
image-20201030172324426

因为有指针的指针存在,所以在使用指针进行重置值时,也需要将相应的*号设置好,例如:

package mainimport "fmt"func main() {name := "武沛齐"// 声明一个指针类型变量p1,内部存储name的内存地址var p1 *string = &name*p1 = "张三" // 将name的内存中的值由 武沛齐 改为 张三// 声明一个指针的指针类型变量p2,内部存储指针p1的内存地址var p2 **string = &p1**p2 = "啦啦啦" // 将name的内存中的值由 张三 改为 啦啦啦var p3 ***string = &p2***p3 = "我靠" // 将name的内存中的值由 啦啦啦 改为 我靠
}

3.4 指针小高级操作

  • 数组的地址 == 数组的第一个元素的地址。
    image-20201030181305133

    dataList := [3]int8{11, 22, 33}fmt.Printf("数组的地址:%p;数组第一个元素的地址:%p \n", &dataList, &dataList[0])
    // &dataList 和 &dataList[0] 的内存中存储的数据虽然相同,但他们是两个不同类型的指针。
    // &dataList 是 *[3]int8 类型
    // &dataList[0] 是 *int8 类型
    
  • 指针的计算

    /*@Author:武沛齐  微信号:wupeiqi666@Description: 老男孩IT教育 & 路飞学城@Video:  https://space.bilibili.com/283478842
    */
    package mainimport ("fmt""unsafe"
    )func main() {dataList := [3]int8{11, 22, 33}// 1.获取数组第一个元素的地址(指针)var firstDataPtr *int8 = &dataList[0]// 2.转换成Pointer类型ptr := unsafe.Pointer(firstDataPtr)// 3.转换成uintptr类型,然后进行内存地址的计算(即:地址加1个字节,意味着取第2个索引位置的值)。targetAddress := uintptr(ptr) + 1// 4.根据新地址,重新转换成Pointer类型newPtr := unsafe.Pointer(targetAddress)// 5.Pointer对象转换为 int8 指针类型value := (*int8)(newPtr)// 6.根据指针获取值fmt.Println("最终结果为:", *value)
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/798527.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

day05 数据类型

day05 数据类型写程序 等价于 写作文数据类型,其实就是各种各样类型的数据。Go语言中常见的数据类型有挺多,例如:整型,用于表示整数。 浮点型,用于表示小数。 布尔型,用于表示真/假。 字符串,用于表示文本信息。 数组,用于表示多个数据(数据集合) 指针,用于表示内存…

前端系列一:HTML

HTML:HyperText Markup Language,超文本标记语言。Author: ACatSmiling Since: 2024-09-10HTML 4 HTML:HyperText Markup Language,超文本标记语言。超文本的含义:是一种组织信息的方式,通过超链接将不同空间的文字、图片等各种信息组织在一起,能从当前阅读的内容,跳转…

day03 基础知识

day03 基础知识 今日概要switch case语句,条件判断。 for循环语句,循环。 goto语法,不太建议使用。 字符串格式化,“拼接”数据。 运算符1.switch语句 package mainfunc main() {// 表达式/*switch 1 + 1 {case 1:fmt.Println("等于1")case 2:fmt.Println("…

day02 快速上手

day02 快速上手 今日概要初识包管理,知道项目中文件和文件、文件和文件夹之间关系。输出,写代码,在go编译器运行时会在屏幕显示内容。初识数据类型整型,数字。例如:1、2、3、4 字符串,表示文本信息。例如:“如家” "锦江之星" 布尔类型,真假。例如: 1>2…

在研究yum源的时候发现的一个小工具

在确保联网的情况下 在终端输入以下命令: bash <(curl -sSL https://gitee.com/SuperManito/LinuxMirrors/raw/main/ChangeMirrors.sh)

day01 环境搭建

day01 环境搭建 Go 和 C语言、C++、Python、Java 等一样都是编程语言。学习任何一门编程语言本质上都分3步走:第一步:安装 解释器 或 编译器。 第二步:学相关编程语言语法,然后写代码。 第三步:用已安装解释器 或 编译器 去运行自己写的代码,这样代码就会去完成我们编写的…

day01 GO环境搭建

day01 环境搭建 Go 和 C语言、C++、Python、Java 等一样都是编程语言。学习任何一门编程语言本质上都分3步走:第一步:安装 解释器 或 编译器。 第二步:学相关编程语言语法,然后写代码。 第三步:用已安装解释器 或 编译器 去运行自己写的代码,这样代码就会去完成我们编写的…

代码随想录算法 - 二叉树7

题目1 669. 修剪二叉搜索树 给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存…

Docker 进阶篇-CIG 重量级监控系统

上一篇讲的是轻量级的监控工具,本文就来讲重量级的: CAdvisor + InfluxDB + Granfana,简称 CIG。上一篇讲的是轻量级的监控工具,本文就来讲重量级的: CAdvisor + InfluxDB + Granfana,简称 CIG。 ​ ‍ ‍ docker stats 原生的 Docker 命令中,stats 可以查看每个容器占用…

macOS Sequoia 15 发布,iPhone 镜像、密码应用程序、窗口平铺更新等带来全新体验

macOS Sequoia 15 发布,iPhone 镜像、密码应用程序、窗口平铺更新等带来全新体验macOS Sequoia 15 发布,iPhone 镜像、密码应用程序、窗口平铺更新等带来全新体验 2024 年 9 月 17 日凌晨 1 点 请访问原文链接:https://sysin.org/blog/macOS-Sequoia/,查看最新版。原创作品…

基于Java+Springboot+Vue开发的大学竞赛报名管理系统

项目简介该项目是基于Java+Springboot+Vue开发的大学竞赛报名管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的大学竞赛报名管理系统项目,大学生可以在…

基于Java+Springboot+Vue开发的蛋糕商城管理系统

项目简介该项目是基于Java+Springboot+Vue开发的蛋糕商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的蛋糕商城管理系统项目,大学生可以在实践中学…