值类型与引用类型


值类型:基本数据类型,比如int,float,bool,string,以及数组和struct这些基本类型,变量直接存储的是一个值,内存通常只在栈中分配,栈在函数调用完会被释放

引用类型:slice,map,chan。它的内部结构有地址以及一些其他值(如长度,容量),因此它不等于一个指针,可以通过取地址操作&获得该引用类型的地址。

切片


切片是拥有相同元素类型的可变长度的序列,支持自动扩容,而且是一个引用类型,它的内部结构有地址,长度和容量,一般用于快速操作一块数据集合。

nilnilnilnil
  • 切片的声明与基本信息
package main

import "fmt"

//切片是相同数据类型,自动扩容的引用类型,定义格式为 var 名称 []数据类型
func main() {

	//定义切片
	var s1 []int //不赋值则默认为空,等于nil,理解为空指针
	var s2 []string
	fmt.Println(s1, s2)
	fmt.Println(s1 == nil)

	//初始化
	s1 = []int{1, 2, 3, 4, 5}
	s2 = []string{"沙河", "清水河", "嘉陵江"}
	//同样可以用简写方式声明切片
	s := []int{1, 9, 9, 7}
	fmt.Println(s1, s2, s)
	fmt.Println(s1 == nil) //因为切片指向的区域有值了,所以不为空了

	//长度和容量
	//因为直接声明的方式得到的切片实际上就是在底层上生成一个相应大小的数组,然后包装为切片返回这个引用类型,所以长度和容量一样大
	fmt.Printf("len(s1)=%d,cap(s1)=%d\n", len(s1), cap(s1))
	fmt.Printf("len(s2)=%d,cap(s2)=%d\n", len(s2), cap(s2))

	//数组得到切片,实际上就是声明一个引用切片指向它,并且可以规定指向的范围,设置容量是为了说明右边界,防止越界报错
	a1 := [...]int{1, 3, 5, 7, 9, 11, 13}
	s3 := a1[0:4] //1,3,5,7;[0,4)对数组进行左闭右开的切割得到切片,即[起始序号,结束序号)
	s4 := a1[1:3] //3,5;[1,3)
	s5 := a1[2:]  //5,7,9,11,13;[2,len(a1))
	s6 := a1[:]   //1,3,5,7,9,11,13;[0,len(a1))
	fmt.Println(s3, s4, s5, s6)
	//长度是包含的元素的个数,容量是切片对应的第一个元素到底层数组的最后一元素包含的所有元素(包括起始和终止元素)的个数
	fmt.Printf("len(s4)=%d,cap(s4)=%d\n", len(s4), cap(s4)) //len=2,cap=7=1~6共6个
	fmt.Printf("len(s5)=%d,cap(s5)=%d\n", len(s5), cap(s5)) //len=5,cap=5=2~6共5个

	//切片再切片,上限边界是容量而不是长度。即起始位置为当前切片的第一个元素为下界,而终止位置是指向的底层数组的最后一个元素
	//切片再切片本质上就是对以前的切片的范围进行修改得到新切片,但是还是指向同一个底层数组
	s7 := s4[3:6]                                           //9,11,13,虽然s4长为2,但是容量为6,所以切割上限为6,起始只要比上限低就行
	fmt.Printf("len(s7)=%d,cap(s7)=%d\n", len(s7), cap(s7)) //len=3,cap=3=3~5共3个
	//直接对底层数组或对指向同一个数组的切片修改,因为切片是引用类型,所以其他以该底层数组为基础的切片里的值都会发生改变
	fmt.Println(s3)
	a1[1] = 200
	s6[0] = 100
	fmt.Println(s3)
    
    var s8 []int         //len(s1)=0;cap(s1)=0;s1==nil
	s9 := []int{}        //len(s2)=0;cap(s2)=0;s2!=nil,因为初始化后分配内存了
	s10 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil,make函数会分配内存
}
func main() {
	a := []int{1, 2, 3, 4, 5, 6}
	a1 := a[0:3]
	fmt.Printf("a1:v:%v,len:%d,cap:%d\n", a1, len(a1), cap(a1)) //a1:v:[1 2 3],len:3,cap:6
	//切片再切片的上限是cap不是len
	a2 := a1[4:6]
	fmt.Printf("a2:v:%v,len:%d,cap:%d\n", a2, len(a2), cap(a2)) //a2:v:[5 6],len:2,cap:2
}
package main

import "fmt"

//make()函数创建切片,可以自定义类型,长度,容量
//make([]T, size, cap),其中T为数据类型,size为切片元素的数量,cap是切片的容量,cap可以不写则默认和size一样
func main() {
	//使用make函数会为切片开辟有size个元素最大为cap的数组
	s1 := make([]int, 5, 10) //默认全为0
	fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d \n", s1, len(s1), cap(s1))
	s2 := make([]int, 0) //长度为0则为空数组
	fmt.Printf("s2=%v len(s2)=%d cap(s1)=%d \n", s2, len(s2), cap(s2))

	//切片的复制拷贝
	s3 := make([]int, 3) //[0 0 0]
	s4 := s3             //将s3直接赋值给s4,s3和s4共用一个底层数组
	s4[0] = 100
	fmt.Println(s3) //[100 0 0]
	fmt.Println(s4) //[100 0 0]

	//切片的遍历
	//索引遍历
	for i := 0; i < len(s3); i++ {
		fmt.Println(s3[i])
	}

	//for range
	for _, v := range s3 {
		fmt.Println(v)
	}

}
package main

import "fmt"

//append扩容切片

func main() {
	//append格式为  需要扩容的切片名 = append (需要扩容的切片名,扩容元素),对原来的扩容就需要返回原来的切片名
	//append的实质就是将为原来需要扩容的切片里的内容再加上扩容的元素开辟一个新的内存空间存储,然后返回一个地址给一个切片名
	//append可以理解为将一个切片的数据加上后面的元素或新的切片进行拼接到一块新的内存然后返回地址
	//append返回给任何一个同类型的切片就行,因为切片是应用类型,不用在不它现在指向的区域有没有值,都可以接受
	s1 := []string{"北京", "上海", "深圳"}
	//零值切片可以直接用无需初始化
	var s2 []string
	var s3 []string
	var s4 []string
	var n []int
	fmt.Printf("s1=%v,len=%d,cap=%d\n", s1, len(s1), cap(s1))
	//新申请的容量小于原来的二倍,所以容量是原来的二倍,重新开辟新的内存空间
	s2 = append(s1, "广州")
	fmt.Printf("s1=%v,len=%d,cap=%d\n,ptr=%p\n", s1, len(s1), cap(s1), &s1[0])
	fmt.Printf("s2=%v,len=%d,cap=%d\n,ptr=%p\n", s2, len(s2), cap(s2), &s2[0])
	//append支持一次性扩容多个元素
	//新申请的容量大于原来的二倍,所以就是新申请的容量
	s3 = append(s1, "广州", "杭州", "成都", "长沙")
	fmt.Printf("s1=%v,len=%d,cap=%d\n", s1, len(s1), cap(s1))
	fmt.Printf("s3=%v,len=%d,cap=%d\n", s3, len(s3), cap(s3))
	//append可以直接添加切片,但需要在切片后加...,...表示拆开的意思
	s := []string{"桂林", "西藏", "柳州"}
	s4 = append(s1, s...)
	fmt.Printf("s4=%v,len=%d,cap=%d\n", s4, len(s4), cap(s4))
	//为零值切片赋值
	n = append(n, 1, 2, 3, 4, 5)
	fmt.Println(n)
	// 从切片中删除元素
	x := [...]int{30, 31, 32, 33, 34, 35, 36, 37}
	a := x[:] //利用数组生产切片
	// 要删除索引为2的元素,加...表示拆开
	//使用这种方法删除实际上就是为其中不需要删除的元素重新排在一起,并且由于剩下的元素个数小于原来的容量,所以不需要重新开辟内存
	//所以相当于就是将需要留下来的元素重新拍好位置,然后放在原来的数组的前面
	a = append(a[:2], a[3:]...)
	fmt.Printf("a=%v,len=%d,cap=%d,ptr=%p\n", a, len(a), cap(a), &a[0]) //[30 31 33 34 35 36 37] cap=8因为还是用的原来的内存
	fmt.Printf("x=%v,len=%d,cap=%d,ptr=%p\n", x, len(x), cap(x), &x[0])
	//[30,31,33,34,35,36,37,37]就是将需要留下的数拍好序后放在数组前,a和x的地址一样的,占用的同样的内存
}

package main

import "fmt"

//copy的格式为copy(目标切片, 源切片)
func main() {

	a1 := []int{1, 3, 5}
	a2 := a1
	a3 := make([]int, 3)
	copy(a3, a1)
	a1[0] = 100
	//因为是把值复制给了a3,所以修改a1影响不到a3
	fmt.Println(a1, a2, a3)
}
package main

import (
	"fmt"
	"sort"
)

//练习
//在初始化为0的切片后使用append
//使用sort函数
func main() {
	//在五个0后面添加0到10
	var a = make([]int, 5, 10)
	for i := 0; i < 10; i++ {
		a = append(a, i)
	}
	fmt.Println(a)

	//在五个空字符后添加字符串0到10
	var b = make([]string, 5, 10)
	for i := 0; i < 10; i++ {
		b = append(b, fmt.Sprintf("%v", i)) //sprintf将数字转为字符串
	}
	fmt.Println(b)

	//排序函数
	a1 := []int{3, 9, 1, 5, 7}
	sort.Ints(a1[:]) //sort只能用于切片,所以需要转换
	fmt.Println(a1)
}