高级数据类型:
高级数据类型有数组、切片、map、指针、结构体、函数、接口、通道等,本文只介绍Arrary、Slice、map、ptr。
数组:
(1)概念:
数组是同一种数据类型的集合。数组从声明时大小就已经确定,使用过程中可以修改值,但是数组的大小不可改变。
(2)初始化
方法一:使用初始化列表来设置数组元素的值
var testArray[3]int
var numArray=[3]int{4,5}
方法二:自行推断数组的长度的方式
var numArray=[...]int{4,5}
方法三:使用指定索引值的方式来初始化数组
var numArray=[...]int{1:4,3:5}
(3)数组的遍历
func main() { var a = [...]string{"南山", "罗湖", "保安"} // 方法1:for循环遍历 for i := 0; i < len(a); i++ { fmt.Println(a[i]) } // 方法2:for range遍历 for index, value := range a { fmt.Println(index, value) } }
(4)多维数组
func main() { a := [3][2]string{ {"南山", "罗湖"}, {"保安", "光明"}, {"龙华", "龙岗"}, } fmt.Println(a) fmt.Println(a[2][1]) //支持索引取值:龙岗 }
注意:多维数组只有第一层可以使用...来让编译器推导数组长度。
切片:
切片是一个拥有相同类型元素的可变长度的序列,它非常灵活,支持自动扩容。切片是一个引用类型,它的内部结构包含:地址、长度、容量。
(1)为什么用切片:
a.数组的内a容固定,不能自动扩展;
b.值传递,b数组作为函数参数时,将整个数组拷贝一份给形参;
go语言中,我们几乎可以在所有的场景中,用切片来替代数组。切片拥有自己的长度和容量,可以通过使用len()、cap()函数分别求切片的长度和容量。
(2)基于数组定义切片:
func main() { // 基于数组定义切片 a := [5]int{15, 26, 37, 48, 59} b := a[1:4] //基于数组a创建切片,包括元素a[1],a[2],a[3] fmt.Println(b) //[26 37 48] fmt.Printf("type of b:%T\n", b) //type of b:[]int c := a[1:] //[26 37 48 59] d := a[:4] //[15 26 37 48] e := a[:] //[15 26 37 48 59] }
(3)切片再切片:
func main() { //切片再切片 a := [...]string{ "南山区", "罗湖区", "光明区", "宝安区", "龙岗区", "龙华区"} fmt.Printf("a:%v type:%T len:%d cap:%d\n", a, a, len(a), cap(a)) b := a[1:3] fmt.Printf("b:%v type:%T len:%d cap:%d\n", b, b, len(b), cap(b)) c := b[1:5] fmt.Printf("c:%v type:%T len:%d cap:%d\n", c, c, len(c), cap(c)) }
(4)使用make()函数构造切片:
上面都是基于数组或切片来创建切片的,如果是动态创建一个切片,则用make([]T, size, cap)
函数
(5)切片的本质:
切片的本质就是对底层数组的封装,一个slice是一个轻量级的数据结构,提供访问数组子序列(或者全部)元素的功能。一个slice由三部分组成:指针、长度(len)、容量(cap),指针指向slice的第一个元素对应的底层数组元素的地址(并不一定是底层数组的首地址)。
(6)切片的赋值拷贝
func main() { s1 := make([]int, 3) //[0 0 0] s2 := s1 //将s1直接赋值给s2,s1和s2共用一个底层数组 s2[0] = 200 fmt.Println(s1) //[200 0 0] fmt.Println(s2) //[200 0 0] }
(7)切片的遍历
切片支持索引遍历和for range遍历
func main() { s := []int{3, 8, 9} for i := 0; i < len(s); i++ { fmt.Println(i, s[i]) } for index, value := range s { fmt.Println(index, value) } }
(8)添加元素
func main() { //append()添加元素和切片扩容 var numSlice []int for i := 0; i < 5; i++ { numSlice = append(numSlice, i) fmt.Printf("%v len:%d cap:%d ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice) } }
扩容策略
a.如果cap(申请容量)大于2倍old.cap(旧容量),则newcap=cap;
b.否则,如果old.cap<1024,则newcap=2*old.cap;
c.否则,如果old.cap>=1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap);
d.如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap);
(9)使用copy()函数复制切片
copy(destSlice,srcSlice[]T)函数可以迅速将一个切片的数组复制到另一个切片空间中
func main() { // copy()复制切片 a := []int{1, 2, 3, 4, 5} c := make([]int, 5, 5) copy(c, a) //使用copy()函数将切片a中的元素复制到切片c fmt.Println(a) //[1 2 3 4 5] fmt.Println(c) //[1 2 3 4 5] c[0] = 20 fmt.Println(a) //[1 2 3 4 5] fmt.Println(c) //[20 2 3 4 5] //说明copy后a和c的底层数组不是同一个数组 }
(10)从切片中删除元素
go语言中没有删除元素的方法,可以使用切片本身的特殊性来删除元素。
func main() { // 从切片中删除元素 a := []int{30, 31, 32, 33, 34, 35, 36, 37} // 要删除索引为2的元素 a = append(a[:2], a[3:]...) fmt.Println(a) //[30 31 33 34 35 36 37] }
要从切片a中删除索引为index的元素,方法是:a=append(a[:index],a[index+1:]...)
map:
(1)概念
map是一种映射关系容器,是一种无序的基于key-value的数据结构,必须初始化后才可以使用,默认初始值为nil,需要make函数来分配内存。
在map里所有的键都是唯一的,而且必须是支持==和!=操作符的类型,对于切片、函数以及包含切片的结构体等类型,由于具有引用语义,不能作为映射的键,键为这些类型编译会报错;map值可以是任意类型,所有键的类型必须相同,值也是如此。
注意:map是无序的,每次打印的顺序都可能不同。
(2)map的基本使用:
func main() { scoreMap := make(map[string]int, 8) scoreMap["张三"] = 200 scoreMap["李四"] = 100 fmt.Println(scoreMap) fmt.Println(scoreMap["李四"]) fmt.Printf("type of a:%T\n", scoreMap) } 输出: map[小明:200 李四:100] 100 type of a:map[string]int
判断某个键是否存在:value,ok:=map[key]
func main() { scoreMap := make(map[string]int) scoreMap["张三"] = 100 scoreMap["李四"] = 200 // 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值 v, ok := scoreMap["张三"] if ok { fmt.Println(v) } else { fmt.Println("没有此人") } }
map的遍历使用 for range
func main() { scoreMap := make(map[string]int) scoreMap["张三"] = 50 scoreMap["李四"] = 150 scoreMap["王五"] = 200 for k, v := range scoreMap { fmt.Println(k, v) } }
注意:遍历map时的元素顺序与添加键值对的顺序无关。
使用delete()函数删除键值对:delete(map,key)
func main(){ scoreMap := make(map[string]int) scoreMap["张三"] = 50 scoreMap["小明"] = 150 scoreMap["李四"] = 200 delete(scoreMap, "小明")//将小明:150从map中删除 for k,v := range scoreMap{ fmt.Println(k, v) } }
求map的键值对的容量,不能用cap()函数,而是用len(),map与slice一样可以自动扩容。
func main() { rand.Seed(time.Now().UnixNano()) //初始化随机数种子 var scoreMap = make(map[string]int, 5) for i := 0; i < 10; i++ { key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串 value := rand.Intn(10) //生成0~10的随机整数 scoreMap[key] = value } for k,v:=range scoreMap{ fmt.Println(k, v) } }
指针:
指针是一个代表某个内存地址值,这个内存地址往往是在内存中存储另一个变量的起始位置。go语言的指针保留的应用不到C/C++ 的1/3,不过还是保留了对基本的内存操作。学习go语言指针只需记住两个符号:&(取地址)、*(根据地址取值/解引用)。
(1)栈帧(stack)
a.用来给函数运行提供的内存空间
b.当函数调用时,产生栈帧,函数调用结束,释放栈帧
c.栈帧存储:局部变量、形参、内存字段描述
d.栈帧的空间使用完后,会自己释放
(2)空指针和野指针
空指针:未被初始化的指针
野指针:被一片无效地址空间初始化
func main() { var a *int//空指针 fmt.Println(*a) var p *int =0xc04204c080//野指针 fmt.Println(*p) }
(3)堆(heap)
make、new的变量存在堆中,堆上申请的空间在c/c++上必须释放的,而go有垃圾回收(gc),但并不是就可以无限申请内存,因为堆的大小是有限的,所以为了能够循环地申请内存空间,在使用完后,或者不使用后,提示gc进行垃圾回收,可以通过赋nil值来提醒gc进行回收。
func new(Type) *Type,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。
func main() { a := new(int) b := new(bool) fmt.Printf("%T\n", a) // *int fmt.Printf("%T\n", b) // *bool fmt.Println(*a) // 0 fmt.Println(*b) // false }
指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。应使用内置的new函数对指针进行初始化之后就可以正常对其赋值了。
func main() { var a *int a = new(int) *a = 10 fmt.Println(*a) }
make也是用于内存分配的,区别于new,它只用于slice、map以及channel的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。
make与new的差异
a.二者都是用来分配内存
b.make只用于slice、map、channel的初始化,返回的是这三个引用类型本身
c.而new用于内存分配,初始值为类型的零值,返回的是指针
(4)变量存储
等号左边的变量,代表变量所指向的内存空间;(写)
等号右边变量,代表变量内存空间存储的数据值;(读)
func main() { var p *int p=new(int) *p=1000 fmt.Printf("%d\n",*p) fmt.Printf("%v\n",*p) }
(5)指针的函数参数
传地址(引用):将形参的地址值作为函数参数
传值(数据):将形参的值拷贝一份给实参
func main() { d := 99 modifty1(d) fmt.Println(d) modifty2(&d) fmt.Println(d) } func modifty1(x int) { x = 100 } func modifty2(x *int) { *x = 100 }
原文地址:https://www.cnblogs.com/juju-niuniu/p/12335481.html