第五章、Go派生/复杂数据类型
1、指针类型
1.1 基本介绍
a、对于基本类型来说,变量存的就是值,也叫值类型,基本数据类型在内存中的布局如下:
b、获取变量的地址,使用&符号。比如,var i int = 10,获取i的地址:&i
c、指针类型,指针变量存的是一个地址,这个地址指向的空间才是存的值;比如:var ptr *int = &num 在这里,带了* ptr是一个指针,没带*,ptr是整数。举例:指针在内存的布局
d、获取指针类型所指向的值,使用 *,比如:var ptr *int ,使用*ptr获取ptr指向的值
1.2 案例演示
1.2.1 写一个程序,获取一个int变量num的地址,并显示在终端
1.2.2 将num的地址赋值给指针ptr,并通过ptr去修改num的值
1.3 指针细节说明
a、值类型,都有对应的指针类型,形式为 *数据类型,比如int对应的指针类型就是 *int;*float32对应的指针类型就是 *float32;按此类推
b、值类型包括:基本数据类型int系列、float系列、bool、string、数组和结构体struct
2、值类型和引用类型
2.1 常见的值类型和引用类型
值类型:基本数据类型int系列、float系列、bool、string、数组和结构体struct
引用类型:指针、slice切片、map、管道chan、接口interface等都是引用类型
2.2 值类型和引用类型使用特点
值类型:变量直接存储,内存通常在栈中(一般生命周期短的分配在栈区,但不绝对是,变量在别的函数用到,有可能放堆区,逃逸了)分配
引用类型:变量存储的是一个地址,这个地址对应的空间才是真正存储的数据(值),内存通常在堆(一般生命周期长的分配在堆区,但不绝对是)上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收
内存栈区和堆区示意图:
3、数组
3.1 基本介绍
数组可以存放多个同一类型数据,数组也是一种数据类型,在Go中,数组是值类型
3.2 数组定义和访问数组元素
方式一、
var 数组名 [数组长度]数据类型
var chicken [5]float64
赋值:chicken[0] = 10 chicken[1] = 20
方式二、
变量名 := [数组长度]数据类型{元素值1,元素值2,...}
a := [3]int{10,20,30}
其他方式::
var chicken [3]int = [3]int{10,20,30}
var chicken = [3]int{10,20,30}
var chicken = [...]int{5,6,7}
指定元素值对应下标
var name = [3]string{1:"tom",2:"LeSeein",3:"yasuo"}
访问数组中元素:chicken[0] chicken[2]
3.3 数组在内存的布局
说明:
a、数组地址可以使用& 取地址获得
b、数组的第一个元素的地址就是数组的首地址
c、数组的各个元素的地址间隔是依据数据的类型决定的,比如int 64位系统占用8个字节
案例演示:
3.4 数组遍历
方式一:for循环遍历
方式二:for - range方式遍历
案例:
3.5 数组使用细节和注意事项
a、数组是多个相同类型数据组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化
b、var arr []int ,这时候arr是一个slice切片
c、数组中的元素可以试任何数据类型,包括值类型和引用类型,但是不能混合使用
d、数组在创建后没有赋值是有默认值的(零值),数值类型默认0 , 字符串类型默认空字符串,bool数组默认false
e、使用数组的步骤: 1.声明数组并开辟空间 2.给各个元素赋值 3.使用数组
f、数组的下标是从0开始的
g、数组下标必须在指定范围内使用,否则报panic:数组元素超出长度,比如var arr [3]int,则有效下标为[0-2]
h、在Go中,数组属于值类型,在默认情况下是值传递,因此会进行值拷贝,数组间不会互相影响
主函数:
test函数:
数组值拷贝内存详解:
i、如果在其他函数中想修改原来数组的值,可以使用引用传递(指针方式)
函数中修改数组值内存详解:
j、长度是数组类型的一部分,在传递函数参数时,需要考虑数组的长度
3.6 数组应用案例
3.7 多维数组
3.7.1 二维数组介绍(这里只介绍二维数组)
3.7.2 二维数组声明、赋值
3.7.3 二维数组应用
3.7.4 二维数组在内存的形式
4、切片
4.1 在go中为什么需要切片?
假如我们需要一个数组来保存学生成绩,但是学生人数不确定的情况又该怎么办呢? 解决方案:使用切片!!!
4.2 切片的基本介绍
a、切片的英文是slice
b、切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制
c、切片的使用和数组类似,遍历切片、访问切片的元素和求切片的长度len(slice)都一样
d、切片的长度是可以变化的,因此切片是一个可以动态变化的数组
e、切片定义的基本语法
var 切片变量名 []类型,比如:var num []int
4.3 切片在内存中的形式
4.4 切片的使用
方式一、定义一个切片,然后让切片去引用一个已经创建好的数组,
方式二、通过make来创建切片,基本语法:var 切片名 []type = make([],len,cap)
说明:type:数据类型 len:大小 cap:指定切片容量,可选,如果分配了cap,则要求cap >= len
内存布局:
以上两种方式轻易切片的区别:
a、通过make方式创建的切片可以指定切片的大小和容量
b、如果没有给切片的各个元素赋值,那么就会使用默认值[int、float=>0 string=>"" bool=>false]
c、通过make方式创建的切片对应的数组是由make底层维护,对外不可见,即只能通过slice去访问各个元素
方式三、定义一个切片,直接指定具体数组,使用原理类似make的方式
4.5 以上前两种方式的区别
a、方式一时直接引用数组,这个数组是事先存在的,程序员是可见的
b、方式二是通过make来创建切片,make也会创建一个数组,是由切片在底层进行维护,程序员是看不见的,make创建的切片示意图如下:
4.6 切片的遍历
for循环遍历和for-range遍历
4.7 切片注意事项和细节说明
a、切片初始化时,var slice = abc[startIndex : endIndex] ,从数组abc下标为 startIndex 开始, 取值到下标为endIndex元素,但不包括endIndex元素
b、切片引用数组时,不能超过最大长度,范围在0~len(abc)之间,但是可以动态增加
var slice = abc[0 : endIndex] 可以简写 var slice = abc[ :endIndex]
var slice = abc[startIndex : len(abc)] 可以简写 var slice = abc[ startIndex : ]
var slice = abc[0: len(abc)] 可以简写 var slice = abc[ : ]
c、cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素
d、切片定义后还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者make一个空间供切片使用
e、切片可以继续切片
e、append()内置函数可以对切片进行动态追加
切片append()操作的底层原理分析:
1、切片append操作的本质是对数组扩容
2、go底层会创建一个新的数组newArr(安装扩容后的大小,再重新赋值给新的切片)
3、将slice原来包含的元素拷贝到新的数组newArr
4、slice重新引用到newArr
5、newArr是在底层来维护,程序员是看不见的
f、切片的拷贝操作,使用copy内置函数实现拷贝
说明:
1、copy(param1,param2)参数类型必须都是切片
2、param1,param2 两个切片空间独立,互不影响
g、元素多的切片拷贝给元素少的切片,一样没问题
4.7 string和slice的使用
4.7.1 string底层是一个byte数组,因此string也可以进行切片处理
4.7.2 string和切片在内存的形式示意图
4.7.3 string是不可变的,也就是说不能通过str[0] = ‘q’方式来修改字符串
4.7.4 如果要修改string字符串中某一个字符的值,可以转切片修改,修改以后再转string
4.7 切片练习
5、排序和查找
5.1 排序基本介绍
排序是指将一组数据,依照指定的顺序进行排列的过程
第一类:内部排序(数据量比较小)
将需要处理的所有数据都加载到内部存储器中进行排序,包括(交换式排序法、选择式排序法和插入式排序法)
案例:
第二轮的第一次比较基于第一轮最后一次比较的结果之上进行,以后每次比较同理...
代码:
第二类:外部排序(数据量比较大)
数据量过大,无法全部加载到内存中,需要借助外部存储进行排序,包括(合并排序法和直接合并排序法)
5.1 查找基本介绍
5.1.1 顺序查找
5.1.2 二分查找(前提:有序数列!!!)
思路分析:
每次折半查找
案例演示:
6、map
6.1 基本介绍
map是key-value的数据结构,又称为字段或者关联数组,官方称之为映射,类似其他编程语言的集合
6.2 基本语法
var map 变量名 map[keytype]valuetype
keytype可以是什么类型::golang中map,key的类型又很多,比如:bool,数字,string,指针,channel,还可以是只包含前面几个类型的接口、结构体、数组, 通常为有int、string
注意:slice、map还有function不可以,因为这几个没法用==来判断
valuetype可以是什么类型::基本跟keytype一样的,通常为数字(整数、浮点数)、字符串、map、struct
注意:map跟数组不一样,数组声明以后就已经分配内存空间地址了
6.3 案例演示
说明:
map在使用前一定要先make;map的key不能重复,重复了就是最后一个key-value值;map的value值可以试相同的;map的key-value是无序的
6.4 map的使用方式
案例:
6.5 map的增删改查操作
a、添加和更新
b、删除
注意:如果我们要删除map的所有key,没有一个专门的方法一次删除,可以遍历一下key,逐个删除,或者map = make(...),make一个新的,让原来的称为垃圾,被gc回收
c、查询
查询有两个返回参数 var,findRes = names["num1"] var就是查找的key对应的value值,findRes查找结果(值为true 和 false)
6.6 map遍历,只能使用for-range结构遍历,map的key不一定都是数字
6.7 map的长度统计,使用len函数
6.8 map切片
基本介绍:
切片的数据类型如果是map,则我们称之为slice of map,map切片,这样使用则map这个数就可以动态变化了
6.9 map排序
基本介绍:
a、golang中没有专门的方法针对map的key进行排序
b、golang中map默认是无序的,注意,也不是按照添加的顺序存放的,你每次遍历,得到的输出可能不一样
c、golang中map的排序,是先将key排序,然后根据key值遍历输出即可
6.10 map使用细节说明
a、map是引用类型,遵守引用类型传递机制,在一个函数接收map,修改后,会直接修改原来的值
b、map容量达到后,再想map增加元素,会自动扩容,并不会发生panic,也就是说,map能动态的增加键值对
c、map的value也经常使用struct类型,更适合管理一些复杂数据类型
案例演示:
7、管道channel
7.1 基本介绍
7.2 管道示意:
7.3 管道基本使用
声明/定义channel:
var 变量名 chan 数据类型 !!!
var perChan chan *Person
var intChan chan int
var mapChan chan map[int]string (用于存放map[int]string类型数据)
说明:
1.channel是引用类型
2.channel必须初始化才能写入数据,即make后才能使用
3.channel是有类型的,intChan 只能写入int类型数据
演示管道的使用:
7.4 channel使用注意事项
a、channel中只能存放指定的数据类型
b、channel中数据放满了就不能再往里面存放数据了
c、如果从channel中取出数据以后就可以继续存放
d、在没有使用协程的情况下,如果channel数据取完了,再取,就会报dead lock
7.5读写channel案例演示
7.6 channel的遍历和关闭