咱们如今有一段程序:算法
package main import "fmt" func main() { // a,b 是一个值 a := 5 b := 6 fmt.Println("a的值:", a) // 指针变量 c 存储的是变量 a 的内存地址 c := &a fmt.Println("a的内存地址:", c) // 指针变量不容许直接赋值,须要使用 * 获取引用 //c = 4 // 将指针变量 c 指向的内存里面的值设置为4 *c = 4 fmt.Println("a的值:", a) // 指针变量 c 如今存储的是变量 b 的内存地址 c = &b fmt.Println("b的内存地址:", c) // 将指针变量 c 指向的内存里面的值设置为4 *c = 8 fmt.Println("a的值:", a) fmt.Println("b的值:", b) // 把指针变量 c 赋予 c1, c1 是一个引用变量,存的只是指针地址,他们两个如今是独立的了 c1 := c fmt.Println("c的内存地址:", c) fmt.Println("c1的内存地址:", c1) // 将指针变量 c 指向的内存里面的值设置为4 *c = 9 fmt.Println("c指向的内存地址的值", *c) fmt.Println("c1指向的内存地址的值", *c1) // 指针变量 c 如今存储的是变量 a 的内存地址,但 c1 仍是不变 c = &a fmt.Println("c的内存地址:", c) fmt.Println("c1的内存地址:", c1) }
打印出:segmentfault
a的值: 5 a的内存地址: 0xc000016070 a的值: 4 b的内存地址: 0xc000016078 a的值: 4 b的值: 8 c的内存地址: 0xc000016078 c1的内存地址: 0xc000016078 c指向的内存地址的值 9 c1指向的内存地址的值 9 c的内存地址: 0xc000016070 c1的内存地址: 0xc000016078
那么a,b
是一个值变量,而c
是指针变量,c1
是引用变量。数组
若是&
加在变量a
前:c := &a
,表示取变量a
的内存地址,c
指向了a
,它是一个指针变量。数据结构
当获取或设置指针指向的内存的值时,在指针变量前面加*
,而后赋值,如:*c = 4
,指针指向的变量a
将会变化。并发
若是将指针变量赋予另一个变量:c1 := c
,那另一个变量c1
能够叫作引用变量,它存的值也是内存地址,内存地址指向的也是变量a
,这时候,引用变量只是指针变量的拷贝,两个变量是互相独立的。app
值变量能够称为值类型,引用变量和指针变量均可以叫作引用类型。数据结构和算法
如何声明一个引用类型的变量(也就是指针变量)呢?函数
咱们能够在数据类型前面加一个*
来表示:指针
var d *int
咱们之后只会以值类型,和引用类型来区分变量。code
有了基本的数据类型,还远远不够,因此Golang
支持咱们定义本身的数据类型,结构体:
// 结构体 type Diy struct { A int64 // 大写导出成员 b float64 // 小写不能够导出 }
结构体的名字为Diy
,使用type 结构体名字 struct
来定义。
结构体里面有一些成员A
和b
,和变量定义同样,类型int64
和float64
放在后面,不须要任何符号分隔,只须要换行便可。结构体里面小写的成员,在包外没法使用,也就是不可导出。
使用结构体时:
// 新建结构体,值 g := diy.Diy{ A: 2, //b: 4.0, // 小写成员不能导出 } // 新建结构体,引用 k := &diy.Diy{ A: 2, } // 新建结构体,引用 m := new(diy.Diy) m.A = 2
能够按照基本数据类型的样子使用结构体,上述创立的:
g := diy.Diy{ A: 2, //b: 4.0, // 小写成员不能导出 }
是一个值类型的结构体。
你也可使用结构体值前面加一个&
或者使用new
来建立一个引用类型的结构体,如:
// 新建结构体,引用 k := &diy.Diy{ A: 2, } // 新建结构体,引用 m := new(diy.Diy) m.A = 2
引用和值类型的结构体有何区别的?
咱们知道函数内和函数外的变量是独立的,当传参数进函数的时候,参数是值拷贝,函数里的变量被约束在函数体内,就算修改了函数里传入的变量的值,函数外也发现不了。
但引用类型的变量,传入函数时,虽然也是传值,但拷贝的是引用类型的内存地址,能够说拷贝了一个引用,这个引用指向了函数体外的某个结构体,使用这个引用在函数里修改结构体的值,外面函数也会发现。
若是传入的不是引用类型的结构体,而是值类型的结构体,那么会完整拷贝一份结构体,该结构体和原来的结构体就没有关系了。
内置的数据类型切片slice
和字典map
都是引用类型,不须要任何额外操做,因此传递这两种类型做为函数参数,是比较危险的,开发的时候须要谨慎操做。
结构体能够和函数绑定,也就是说这个函数只能被该结构体使用,这种函数称为结构体方法:
// 引用结构体的方法,引用传递,会改变原有结构体的值 func (diy *Diy) Set(a int64, b float64) { diy.A = a diy.b = b return } // 值结构体的方法,值传递,不会改变原有结构体的值 func (diy Diy) Set2(a int64, b float64) { diy.A = a diy.b = b return }
只不过在之前函数的基础上func Set(a int64, b float64)
,变成了func (diy *Diy) Set(a int64, b float64)
,只不过在函数里面,可使用结构体变量diy
里面的成员。
上面表示值类型的结构体diy Diy
可使用Set2
方法,引用类型的结构体diy *Diy
可使用Set
方法。
若是是这样的话,咱们每次使用结构体方法时,都要注意结构体是值仍是引用类型,幸运的是Golang
操碎了心,每次使用一个结构体调用方法,都会自动将结构体进行类型转换,以适配方法。好比下面:
// 新建结构体,值 g := diy.Diy{ A: 2, //b: 4.0, // 小写成员不能导出 } g.Set(1, 1) fmt.Printf("type:%T:%v\n", g, g) // 结构体值变化 g.Set2(3, 3) fmt.Printf("type:%T:%v\n", g, g) // 结构体值未变化 // 新建结构体,引用 k := &diy.Diy{ A: 2, } k.Set(1, 1) fmt.Printf("type:%T:%v\n", k, k) // 结构体值变化 k.Set2(3, 3) fmt.Printf("type:%T:%v\n", k, k) // 结构体值未变化
结构体g
是值类型,原本不能调用Set
方法,可是Golang
帮忙转换了,咱们毫无感知,而后值类型就变成了引用类型。同理,k
是引用类型,照样可使用Set2
方法。
前面咱们也说过,函数传入引用,函数里修改该引用对应的值,函数外也会发现。
结构体的方法也是同样,不过范围扩散告终构体自己,方法里能够修改结构体自己,可是若是结构体是值,那么修改后,外面的世界是发现不了的。
关键字new
主要用来建立一个引用类型的结构体,只有结构体能够用。
关键字make
是用来建立和初始化一个切片或者字典。咱们能够直接赋值来使用:
e := []int64{1, 2, 3} // slice f := map[string]int64{"a": 3, "b": 4} // map
可是这种直接赋值相对粗暴,由于咱们使用时可能不知道数据在哪里,数据有多少。
因此,咱们在建立切片和字典时,能够指定容量大小。看示例:
s := make([]int64, 5) s1 := make([]int64, 0, 5) m1 := make(map[string]int64, 5) m2 := make(map[string]int64) fmt.Printf("%#v,cap:%#v,len:%#v\n", s, cap(s), len(s)) fmt.Printf("%#v,cap:%#v,len:%#v\n", s1, cap(s1), len(s1)) fmt.Printf("%#v,len:%#v\n", m1, len(m1)) fmt.Printf("%#v,len:%#v\n", m2, len(m2))
运行后:
[]int64{0, 0, 0, 0, 0},cap:5,len:5 []int64{},cap:5,len:0 map[string]int64{},len:0 map[string]int64{},len:0
切片可使用make([],占用容量大小,所有容量大小)
来定义,你能够建立一个容量大小为5
,可是实际占用容量为0
的切片,好比make([]int64, 0, 5)
,你预留了5
个空间,这样当你切片append
时,不会由于容量不足而内部去分配空间,节省了时间。
若是你省略了后面的参数如make([]int64, 5)
,那么其等于make([]int64, 5,5)
,由于这时所有容量大小就等于占用容量大小。内置语言cap
和len
能够查看所有容量大小,已经占用的容量大小。
同理,字典也能够指定容量,使用make([],容量大小)
,可是它没有所谓的占用容量,它去掉了这个特征,由于咱们使用切片,可能须要五个空白的初始值,可是字典没有键的状况下,预留初始值也没做用。省略容量大小,表示建立一个容量为0
的键值结构,当赋值时会自动分配空间。
函数是代码片断的一个封装,方法是将函数和结构体绑定。
可是Golang
里面有一些内置语法,不是函数,也不是方法,好比append
,cap
,len
,make
,这是一种语法特征。
语法特征是高级语言提供的,内部帮你隐藏了如何分配内存等细节。
我是陈星星,欢迎阅读我亲自写的 数据结构和算法(Golang实现),文章首发于 阅读更友好的GitBook。