Go语言入门系列前面的文章:算法
若是你使用过C或C++,那你确定对指针这个概念不陌生。编程
咱们须要先介绍两个概念:内存和地址。数组
咱们写的代码都存储在外存(C盘、D盘)中,好比我存在了D:\Work\Program\go
目录下。若是你想要运行你的代码,必须先把你的代码加载进内存中,而后交给CPU执行计算,而CPU计算的结果也会存到内存中。数据结构
内存的存取速度快,其中有许多存储单元用来存储数据,CPU能在内存中直接找到这些数据。这是由于内存中的每一个位置都有一个独一无二的地址标识。能够把内存当作一幢有许多房间的大楼,每一个存储单元是一个房间,存储的数据是房间中的物品,地址就是房间号。app
因此对CPU来讲,若是想找到某个房间中的物品(从内存中取数据),或者向某个房间中放物品(向内存中存数据),咱们必须知道房间号(内存地址)。数据结构和算法
内存地址一般是一串16进制的数字,若是写代码时存个整数1或取个整数1都须要写这么一串数字,那太麻烦了。因此高级语言为咱们提供了一个便利,用咱们人能记住的“名字”来代替这串数字。函数
这些“名字”就是变量名。.net
var a int = 1 var b int = 2 var c int = 333 var d int = 6666
变量名和地址的关联由编译器替咱们作,硬件访问的仍然是内存地址。指针
简单地来讲,指针也是一个变量,只不过这个变量中存的不是咱们日常用到的一、二、三、"Hello"、true等值,而是其余变量的地址。code
之因此取名指针,是由于指针变量b
中保存了变量a
的地址,咱们能够经过该指针变量b
找到变量a
,若是画图看起来,看起来就像是指针b
指向了变量a
。
还能够有指针的指针:
声明一个指针:
var p *int
*int
表示p
是一个int
类型指针,p
指针中存的是一个int
类型变量的地址,这意味着p
中不能存其余类型变量的地址。
如何获取某个变量的地址呢?使用操做符&
:
var a int = 66 //a是值为66的int变量 p = &a //将a的地址赋给指针p
那么如何根据指针中的地址找到对应的变量呢?使用操做符*
:
var b = *p //根据p中的值找到a,将其值赋给b fmt.Println(b) //66 *p = 99 //根据p中的值找到a,改变a的值 fmt.Println(a) //99
必定要注意指针的初始化,若是不初始化,则指针的的值是其零值——nil
。对未初始化的指针赋值,则会出问题:
var p *int //只声明,未初始化 *p = 12 //报错:invalid memory address or nil pointer dereference
缘由是指针p
中没值,是个nil
,天然就没法根据地址找到变量。若是你想使用指针,必须先确保你的指针中有合法的内存地址才行。应当这样写:
var a int var p *int = &a //p被初始化为a的地址 *p = 12 //根据p的值找到a,12赋值给a //或者 var a int var p *int p = &a //a的地址赋给p *p = 12 //根据p的值找到a,12赋值给a
下面是一个完整的例子:
package main import "fmt" func main() { var a int = 66 //变量a var p *int = &a //指针p指向a var b = *p //获取p指向的变量a的值 fmt.Println("a =",a, ", b =", b, ", p =", p) fmt.Println("a的地址 =", &a, ", b的地址 =", &b, ", p的地址 =", &p) *p = 12 //改变p指向的变量a的值 fmt.Println("a =",a, ", b =", b, ", p =", p) fmt.Println("a的地址 =", &a, ", b的地址 =", &b, ", p的地址 =", &p) var pp **int = &p //指针pp指向指针p var c = *pp //获取pp指向的p的值 var d = **pp //获取pp指向的p指向的a的值 fmt.Println("pp =", pp, ", c =", c, ", d =", d) fmt.Println("pp的地址 =", &pp, ", c的地址 =", &c, ", d的地址 =", &d) }
和C语言同样,Go语言中也有结构体。
结构体就是一组字段/属性的集合。有告终构体,咱们能够根据本身的需求定义本身的类型。好比狗,确定不能用基本数据类型来表示,由于狗身上有许多属性:string
类型的姓名、int
类型的年龄等等,狗是一个拥有许多属性的集合,换句话说,狗是一个结构体。咱们能够定义一个dog
类型的结构体来表示狗。
结构体的声明方式:
type 结构体名字 struct { 字段名1 类型1 字段名2 类型2 ... }
下面是结构体dog
的声明:
type dog struct { name string age int }
声明告终构体后,就可使用它。
首先,只要你正确声明告终构体后,你就能像使用int
、string
等基本类型声明变量同样去声明dog
类型的变量,而后,你就能给声明的变量d
的字段赋值了,经过点号.
来访问结构体的字段:
var d dog //声明一个dog类型的变量d d.name = "哮天犬" d.age = 3
除此以外,还有几种声明方式。
你能够按照字段顺序直接赋值
d := dog{"哮天犬", 3}
或者指定字段赋值,这样能够忽略字段顺序:
d := dog{age:3, name:"哮天犬"}
下面是一个完整的例子:
package main import "fmt" type dog struct { name string age int } func main() { var d dog //声明一个dog类型的变量d d.name = "哮天犬" d.age = 3 d1 := dog{"哮地犬", 2} d2 := dog{age:4, name:"哮人犬"} fmt.Println(d, d1, d2) }
咱们能够获取结构体的指针:
d := dog{"哮地犬", 2} p := &d //获取到d的地址
能够根据结构体指针访问其字段:
n := (*p).name fmt.Println(n) //哮天犬
这种方式比较麻烦,Go语言提供了隐式间接引用:
n := p.name //这样也行 fmt.Println(n)
咱们能够经过new
函数给结构体分配一个指针。
先介绍一下new
函数:new
函数用于给各类类型的内存分配。new(T)
会给T
类型分配对其合适的内存空间,用T
类型的零值填充,并返回其地址,是一个*T
类型的值。换句话说,该函数会返回一个指向T
类型零值的指针。
p := new(dog) fmt.Printf("%T\n", p) //*main.dog fmt.Println(p) //&{ 0} fmt.Println(*p) //{ 0}
从上面打印的三行语句中也能够看出,new(dog)
返回的是一个指针。
一个结构体也能够做为另外一个结构体的字段,下面是一个例子:
package main import "fmt" type people struct { name string age int d dog } type dog struct { name string age int } func main() { a := people{"行小观", 18, dog{"小狗", 2}} fmt.Println(a) //{行小观 18 {小狗 2}} fmt.Println(a.d) //{小狗 2} fmt.Println(a.name) //行小观 fmt.Println(a.d.name) //小狗 }
也可使用匿名字段,何为匿名字段?顾名思义,只提供类型,不写字段名:
package main import "fmt" type people struct { name string age int dog //匿名字段 } type dog struct { name string age int } func main() { a := people{"行小观", 18, dog{"小狗", 2}} fmt.Println(a) //{行小观 18 {小狗 2}} fmt.Println(a.dog) //{小狗 2} fmt.Println(a.name) //行小观 fmt.Println(a.dog.name) //小狗 }
我是「行小观」,于千万人中的一个普通人。阴差阳错地走上了编程这条路,既然走上了这条路,那么我会尽量远地走下去。
我会在公众号『行人观学』中持续更新「Java」、「Go」、「数据结构和算法」、「计算机基础」等相关文章。
欢迎关注,咱们一块儿踏上行程。
本文章属于系列文章《Go语言入门系列》。
若有错误,还请指正。