package main
import (
"errors"
"fmt"
"unsafe"
)
func defaultValue() {
var a int
var b byte
var c bool
var d string
var e rune
var f []int
var g struct{}
var h float64
var com complex64
com = complex(3, 5)
var com2 complex128
com2 = complex(3, 5)
fmt.Printf("defaultValue %d\n", a)
fmt.Printf("defaultValue %d\n", b)
fmt.Printf("defaultValue %t\n", c)
fmt.Printf("defaultValue [%s]\n", d)
fmt.Printf("defaultValue %c\n", e)
fmt.Printf("defaultValue %d\n", f)
fmt.Printf("defaultValue %d\n", g)
fmt.Printf("defaultValue %f\n", h)
fmt.Print("字符串长度")
fmt.Println(unsafe.Sizeof(d))
fmt.Println(com)
//实部和虚部是浮点数
//real实数内置函数返回复数 c 的实部,返回值将是与 c 类型对应的浮点类型。
//imag 内置函数返回复数的虚部数字 c.返回值将是浮点类型对应c 的类型。
fmt.Printf("%T %T\n", real(com), imag(com))
fmt.Printf("%T %T\n", real(com2), imag(com2))
}
func main() {
//defaultValue()
defaultV()
var bb = 100
fmt.Printf("%T\n", bb)
var bbb byte = 100 //byte等价于uint8
fmt.Printf("%T\n", bbb)
var cc rune = '中' //rune等价于int32
fmt.Printf("%T\n", cc)
var dd rune = '😊' //unicode
fmt.Printf("%T\n", dd)
fmt.Println('😀') //128512
errType()
structType()
}
func defaultV() {
var a int
var pointer unsafe.Pointer = unsafe.Pointer(&a) //指向a
var ppt uintptr = uintptr(pointer) //无符号指针
var pit *int = &a //指向a
fmt.Printf("pointer %p ,ppt %d %x,pit %p\n", pointer, ppt, ppt, pit)
}
func errType() {
var err error
err = errors.New("这就是一个错误的描述")
fmt.Printf(" %v\n", err) // 这就是一个错误的描述
}
func structType() {
type Person struct {
name string
age int
}
var per = Person{"张三", 18}
fmt.Printf("%v\n",per)//打印值
fmt.Printf("%+v\n",per)//带字段打印值
fmt.Printf("%#v\n",per)//带字段,而且还会打印出带包的方法
}
}
我们在源码里会看到 关于类型的定义—>>有一个专门的go文件:用于定义我们的数据类型;
比如:
type signal uint8//定义一个信号量
type mmap map[string]string //定义一个map
type add func(a,b int) int//定义函数
type man struct { //定义结构体
name string
age int
}
type comp complex64
每一种自定义类类型都可以有自己的方法,当我们给自定义类型赋值时(有的需要初始化),可以调用相应的方法;
如下代码可以参考:
type signal uint8 //定义一个信号量
type mmap map[string]string //定义一个map
type add func(a, b int) int //定义函数
type man struct { //定义结构体
name string
age int
}
type comp complex64
func (m mmap) Eat() {
fmt.Printf("中国首都 %s\n", m["中国"])
//m.Eat() //这是回调
}
func (app add)test(a ,b int) {
result:=a+b
fmt.Printf("%d\n",app(result,10))
}
func (you man) SayHi(say string) {
fmt.Printf("my name is %s 年龄是 %d", you.name, you.age)
}
func main() {
var u man
u = man{name: "小明", age: 19}
u.SayHi("大家好")
var demo mmap//如果不初始化map,就会得到一个nil map,这种map不会用来存放信息
demo=make(map[string]string)
demo["中国"]="北京"
demo["hello"] = "world"
demo.Eat()
var cse add
cse= func(a, b int) int {//就是重写函数
return a+b
}
cse.test(10,20)
defaultV()
//声明数组
var arr0 []int //如果不在这里声明长度,下面又直接指定数组中某个元素的值,会报错 panic: runtime error: index out of range [0] with length 0
arr0[0]=100 //这里会报错
arr0=[]int{2,3,4} //其实跟java一样的声明数组的方式
fmt.Println(arr0)
var arr = [5]int{}
arr[0] = 2
var arr1 = [5]int{2, 3}
fmt.Printf("%d\n", arr1[1])
var arr2 = [5]int{2: 12, 4: 233}//可以为指定位置的元素附上值
fmt.Println(arr2)
var arr3 = []struct { //结构体数组
name string
age int
}{{"小明", 12}, {"小红", 13}}
fmt.Println(arr3)
//有下面的写法,可以不写数组的长度,通过反推得出数组长度
var arr2 = []int{2: 12, 4: 233}//像这种,最好写上数组长度
fmt.Println(arr2)
var arr3 = [...]struct {//也可以通过...的方式写上数组的长度----反推出数组的长度
name string
age int
}{{"小明", 12}, {"小红", 13}}
fmt.Println(arr3)
二维数组:
//二维数组
var array1=[4][6]int{{1,2,3,4,5,6},{8,9}}
fmt.Println(array1)
//二维数组只有第一维可以推断
var array2=[][5]int{{1,2,3,4},{5,6,7}}
fmt.Println(array2)
fmt.Println(array2[1][3])
通过下标进行访问
fmt.Println(arr2[3])
fmt.Println(array2[1][3])
var stu [10]int=[10]int{1,2,3,4,5,6,7,8,9,10}
for i := 0; i < len(stu); i++ {
fmt.Printf("下标stu[ %d] 值为%d\n",i,stu[i])
}
for i, ele:=range stu {
fmt.Printf("下标stu[ %d] 值为%d 那stu[%d]\n",i,ele,stu[i])
}
for _, ele:=range stu {
fmt.Printf("%d\n", ele)
}
这个其实跟java的代码写法类似:
fmt.Println("----------------------")
var fast = [4][5]int{{1, 2, 3, 4, 5}, {5, 4, 3, 2, 1}, {6, 7, 8, 9}, {9, 8, 7, 6}}
//遍历二维数组
for i := 0; i < len(fast[i])-1; i++ {
for j := 0; j < len(fast[j]); j++ {
fmt.Println(fast[i][j])
}
}
fmt.Println("--------------------------------")
for row, arr := range fast {
for col, ele := range arr {
fmt.Printf("a[%d][%d]=%d\n", row, col, ele)
}
}
关于数组的几个方法:
由于数组在初始化之后长度不会变了,不需要给他预留空间,所以cap(stu)=len(stu)
var stu [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println(cap(stu))//容量 10
fmt.Println(len(stu))//长度10
cap()方法的解释:
//数组:v中的元素个数(与len(v)相同)。
//指向数组的指针:*v中的元素数量(与len(v)相同)。
//切片:切片时所能达到的最大长度;
//如果v是空的,cap(v)是零。
// channel:通道缓冲区容量,以元素为单位;
//如果v是空的,cap(v)是零。
len()方法的解释:
/ array: v中元素的个数。
//指向数组的指针:*v中的元素数量(即使v为nil)。
//切片或映射:v中的元素数量;如果v为nil, len(v)为零。
//字符串:v。
// channel:通道缓冲区中排队(未读)的元素数量;
//如果v为空,len(v)为零。
数组的长度和类型都是数组类型的一部分,函数传递数组类型时这两部分都必须吻合
即声明一个数组 要求必须指定长度 即[8]int
为什么这么说呢?
我们来看一个代码案例:
func avgMethod(haha[10]int) float64 {
var sum int
for i := 0; i < len(haha); i++ {
sum+=haha[i]
}
return float64(sum / len(haha))
}
func main() {
//var tea []int
var stu [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Printf("%f\n",avgMethod(stu))
}
现在如果我们在方法avgMethod方法中修改数组的值,其实是不影响原数组stu的,因为传入参数时是值的拷贝,而不是数组自身被传进去了,这跟java中是一样的,值传递;
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
切片跟数组很像,但是切片不用像数组哪一样要指定长度;
数组 var arr=[10]int{}
切片 var sli []int=[]int{}
当我们声明数组时,如果没有给数组中元素赋值,则为默认的值, 如果是切片,则并不会给他赋值;
package main
import (
"fmt"
"unsafe"
)
// 切片的地址跟数组首元素的地址是两码事
type slice struct { //切片由三部分组成
array unsafe.Pointer
len int
cap int
}
func main() {
var s []int //声明一个切片 len=cap=0
s = []int{1, 2, 3} //切片初始化方式1(数组要指定长度的,所以这是切片)
fmt.Println(s)
var ss[]byte
ss = make([]byte, 3) //初始化 ,len=cap=3
//可以不屑写cap,不写则默认len=cap--即下面的方式
ss=make([]byte, 3,3)
fmt.Println(ss)
sss:=make([]rune,3,5)//初始化 ,len=3,cap=5
fmt.Printf("%p\n",&sss)
}
二维切片:
//二维数组
arr:=[3][]int{{1},{2,3,4}}
//二维切片
slice2d := [][]int{{1}, {2, 3, 4}}
fmt.Println(arr)
fmt.Println(slice2d)
结果可以看看到,二维切片与二维数组的区别:
二维切片各行的len可以不相等,但是二维数组各行的len要相等
cap 与len
sss := make([]int, 3, 5) //初始化 ,len=3,cap=5
fmt.Println(sss)
fmt.Println(len(sss))
fmt.Println(cap(sss))
数组一旦声明,他的容量就被限制了,而切片就好像java中的集合---->>
随着元素的个数增加,他的len也在增加,他的cap也会发生变化
//切片的扩容
var numbers []int
for i := 0; i < 10; i++ {
numbers = append(numbers, i)
fmt.Printf("len: %d cap: %d pointer: %p\n", len(numbers), cap(numbers), numbers)
}
切片现在时这个状态:
当cap大于1024时他的扩容
//切片的扩容
var numbers []int
for i := 0; i < 1280; i++ {
numbers = append(numbers, i)
fmt.Printf("len: %d cap: %d pointer: %p\n", len(numbers), cap(numbers), numbers)
}
//向切片中追加元素
numbers=append(numbers,11,12,13,14,15,16,17)
numbers=append(numbers,18)
fmt.Printf("len: %d cap: %d pointer: %p\n", len(numbers), cap(numbers), numbers)
为了更直观的查看扩容机制—请看下面的代码:
func KuoRong() {
s := make([]int, 0, 10)
//扩容前的切片中数组的长度与切片容量
preCap := cap(s)
for i := 0; i < 1185; i++ {
s = append(s, 0)
//追加元素后的切片中数组的长度与切片容量
currCap := cap(s)
if currCap > preCap {
fmt.Printf("Cap %d 扩容到了 %d\n", preCap, currCap)
//以前的,现在的
preCap = currCap
}
}
}
append方法的描述:
append内置函数在片的末尾追加元素。如果它有足够的容量,目的地被切片以容纳新元素。如果没有,将分配一个新的底层数组。
Append返回更新后的片。因此,有必要存储append的结果,通常在保存切片本身的变量中: slice = append(slice, elem1, elem2) slice = append(slice, anotherSlice…)
作为特殊情况,它是合法的附加字符串到字节片,像这样: slice = append([]byte("hello "), “world”…)
小结:
切片相对于数组,他的特点是可以追加元素,可以自动扩容,追加的元素放到预留的内存空间内,同时len增加1
如果预留空间用完,则会重新申请一块更大的内存空间---->>
cap变为之前的2倍或者1.**倍 ,之后把原内存空空间的数据拷贝过来,在新内存空间上执行append操作具体的倍数还不一致,大体知道这个就可以了
func SonSlice() {
//定义一个切片
arr := make([]int, 5, 8)
for i := 0; i < len(arr); i++ {
arr[i]=i
}
fmt.Println(arr)
//截取子切片
sub:=arr[0:2]
fmt.Println(sub)
}
当我们修改子切片中的内容时:
母切片中的内容也会发生改变
sub[0]=100
sub[1]=102
fmt.Println(arr)
子切片与母切片共享内存空间
当我们打印母切片与子切片的地址时:
fmt.Printf("%p\n",arr)
fmt.Printf("%p\n",sub)
两个的地址是一样的;
这是因为子切片而引用了切片中数组的首元素,所以地址是一样的
如果切片不是引用数组首元素,像这样—>>
//定义一个切片
arr := make([]int, 5, 8)
for i := 0; i < len(arr); i++ {
arr[i]=i
}
fmt.Printf("%p\n",arr)
fmt.Println(arr)
//截取子切片
sub:=arr[2:4]
fmt.Println(sub)
sub[0]=100
sub[1]=102
fmt.Println(arr)
fmt.Printf("%p\n",arr)
fmt.Printf("%p\n",sub)
当我们的母切片容量满了的时候:
func SonSlice() {
//定义一个切片
arr := make([]int, 5, 8)
for i := 0; i < len(arr); i++ {
arr[i]=i
}
fmt.Printf("%p\n",&arr)//这里也是最新的地址
fmt.Println(arr)
//截取子切片
sub:=arr[0:2]
fmt.Println(sub)
//母切片arr满了时,再添加一个元素
arr=append(arr,10)
arr=append(arr,11)
arr=append(arr,12)
//查看地址
fmt.Printf("%p\n",&arr)
fmt.Printf("%p\n",&sub)
此时我们修改子切片的内容
sub[0]=100
sub[1]=102
//打印母切片的内容
fmt.Println(arr)
发现依然被修改了,这说明母切片与子切片的联系并没有被切断(这是因为此时还没有扩容)
如果扩容了
贴一段完整的代码:
func SonSlice() {
//定义一个切片
arr := make([]int, 5, 8)
for i := 0; i < len(arr); i++ {
arr[i]=i
}
fmt.Printf("%p\n",arr)
fmt.Println(arr)
//截取子切片
sub:=arr[2:4]
fmt.Println(sub)
sub[0]=100
sub[1]=102
fmt.Println(arr)
fmt.Printf("%p\n",arr)
fmt.Printf("%p\n",sub)
//母切片arr满了时,再添加四个元素
arr=append(arr,10)
arr=append(arr,11)
arr=append(arr,12)
//内存分离
arr=append(arr,13)
//查看地址
fmt.Printf("%p\n",&arr)
fmt.Printf("%p\n",&sub)
//此时修改母切片中的值
arr[2]=9999
arr[3]=9999999
fmt.Println(sub)//100,102
//那如果是求改子切片的数据会不会影像母切片呢
sub[0]=100
sub[1]=102
fmt.Println(arr)
}
当母切片扩容时,原母切片会与当时的子切片进行内存的分离,即此时修改母切片/子切片都会互不影响;
即切片是对数组的一种引用,但又不是数组,只是指针都指向同一块内存空间;
所以,go语言中函数传参传的都是值,即传切片会把切片中的字段(指针,长度,容量)都拷贝一份传进来;
由于传的是底层数组的指针,所以是可以直接修改底层数组的元素的;
我们来看一段代码案例:
func main(){
var arr []int = []int{1, 2, 3}
test(arr)
fmt.Println(arr)
var arr2 = [3]int{
1, 2, 3,
}
test2(arr2)
fmt.Println(arr2)
// 这个传的是切片
func test(arr []int) {
arr[1] = 100
fmt.Printf("%s %d\n", "在这个方法中", arr[1])
}
// 这个传的是数组
func test2(arr [3]int) {
arr[1] = 100
fmt.Printf("%s %d\n", "在这个方法中", arr[1])`在这里插入代码片`
}
如果是切片,我们发现原切片中的值已经被修改了,如果是数组,则原数组中的值没有被修改
go中的map 底层实现是hash table 根据key查找valuede 时间复杂度是O(1)
key1 根据hash算法算出一个hash值,然后对槽位总数取模,得到的值,就将这个值插入到这个位置;
当hash冲突太多时,需要扩容来解决,即增加槽位数,给每个key重新分配槽位数;
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。
func MapTest() {
//声明一个map
var map1 map[string]string
//需要实例化
map1=make(map[string]string,8)
map1["姓名"]="小花"
fmt.Println(map1)
map2:= make(map[string]string,8)
map2["1"] = "小明"
map2["2"] = "小明"
map2["3"] = "小明"
map2["4"] = "小明"
map2["5"] = "小明"
map2["6"] = "小明"
map2["7"] = "小明"
map2["8"] = "小明"
for name:=range map2{
fmt.Printf("%s %s\n" ,name,map2[name])
}
//删除元素
delete(map2,"8")//删除key为1的元素
for name:=range map2{
fmt.Printf("%s %s\n" ,name,map2[name])
}
}
当key重复时—>>
func MapTest() {
//声明一个map
var map1 map[string]string
//需要实例化
map1 = make(map[string]string, 8)
map1["姓名"] = "小花"
fmt.Println(map1)
map2 := make(map[string]string, 8)
map2["1"] = "小明"
map2["2"] = "小明"
map2["3"] = "小明"
map2["4"] = "小明"
map2["5"] = "小明"
map2["6"] = "小明"
map2["8"] = "小明1"
map2["8"] = "小明2"
//有重复的key,后来的替换掉之前的
fmt.Println("有重复的key", map2)
//这里证明是无序的
for name := range map2 {
fmt.Printf("%s %s\n", name, map2[name])
}
fmt.Println(len(map2))
//删除元素
delete(map2, "8") //删除key为8的元素
fmt.Println("在这里", map2)
for name := range map2 {
fmt.Printf("%s %s\n", name, map2[name])
}
}
查找map中的元素:
第一种方式—>>fmt.Println("查找key为 10",map2["10"])
直接输出该key对应的值,但是有一点,是要明确map中的数据是非空或者有其他限制的,不然无法确定查找的map[key],如果是默认值呢?
第二种方式---->>
//根据key找value
if value ,exists:=map2["100"];exists{
fmt.Println(value)
}else{
fmt.Println("不存在")
}