之前学的是java,现在学go,突然给我整个指针给我整的有点不太适应
经过过一段时间学习,发现指针并没有那么难
首先说一下,什么是指针?
指针变量,就是一个存放地址的变量。
何谓地址?地址就是一个变量它在内存中所在的位置
func main() {
a := 10
// 这里的 b 就是指针变量
b := &a // “&” 是取地址的意思,这里就是把a变量值的地址拿出来,给变量b
fmt.Println(&a) // 0xc000018098
fmt.Println(&b) // 0xc000006028 变量都有地址,指针变量作为一个变量,当然也有自己的地址咯
fmt.Println(b) // 0xc000018098
fmt.Println(*b) // 10
// “*”是取地址对应的变量值
a = 11
fmt.Println(*b) //11
}
指针类型
除了上篇说的那么多类型之外,还有一种类型叫做指针类型
上面不是说过指针是什么东西了吗?这里为什么还要再说呢?
虽然说指针变量存的是地址,但也不能随便往里面放地址。只能放指针类型对应变量的地址
Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如:*int、*int64、*string等。
现在看这样一个例子
a := 11 // a是int型 c := "RZZY" // c 是string型 d := &a // d 以这种方式存放a地址,d现在就是 *int 型 // 现在我想让d改存string型变量的地址,做不到了! d = &c // 无法将 '&c' (类型 *string) 用作类型 *int结构体
结构体,它就像一个箱子,里面放着各种东西
Go语言中是通过结构体来实现面向对象的
type Person struct{
name, gender string
age int
}
这么一个结构体,描述的就是一个 人
而结构体里的字段,不就是属性么!
像不像“类”?
“类”有了,我们就该实例化了
var 结构体实例 结构体类型
var p1 Person p1.name = "RZZY" p1.gender = "Man" p1.age = 180
这样一个实例就有了,但现在和java类的实例化还是有点不太一样,因为java的对象是引用类型的,而我们现在实例化出来的结构体,直接打印一下,发现不太对,这结构体怎么和java的对象不一样,打印出来的不是地址?
加上go语言参数传递是值传递,如果我们把这个“对象”传递给函数,函数会复制一个这个"对象"的副本,之后所有的修改都不会影响到原来的这个"对象"
type Person struct {
Name, Gender string
Age int
}
func main() {
var p1 Person
p1.Name = "RZZY"
p1.Gender = "Man"
p1.Age = 180
fmt.Println(p1) // {RZZY Man 180}
modifyP(p1)
fmt.Println(p1) // {RZZY Man 180} 完全没有变化
}
func modifyP(p Person) {
p.Name = "RZXY"
}
有问题就有解决方法,我们能想到的就是,直接传递指针类型不就好了!
指针类型变量的值就是地址,修改的时候是根据指定地址的值进行修改,这样就能做到修改原"对象"
func main() {
var p1 Person
p1.Name = "RZZY"
p1.Gender = "Man"
p1.Age = 20
fmt.Println(p1) // {RZZY Man 180}
modifyP(&p1) // 这里的参数将原来的(p1)改成了(&p)
fmt.Println(p1) // {RZXY Man 180} 修改成功!
}
func modifyP(p *Person) { // 这里的参数将原来的(p Person)改成了(p *Person)
p.Name = "RZXY"
}
好了,接下来就是下一个东西,结构体的指针类型!
创建指针类型的结构体通过使用new()来对结构体进行实例化,结构体对象会被分配内存,而得到的就只是“对象”的地址
var p2 = new(Person)
fmt.Println(p2) // &{ 0} 字段默认是对应类型的零值
我们注意到,前面多了个"&",也就是取地址符号!
这样我们在传递参数的时候,可以直接传递这个指针类型的结构体
结构体字段的可见性!结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)
函数与结构体既然属性有了,方法也该有,但是在方法之前要先了解一下Go里的函数
函数func 函数名(参数列表) (返回值列表){
函数体...
}
func Hello(a, p string) { // 类型相同的两个参数可以只写一次类型
fmt.Printf("%s岁的%s向你问好!n", p, a)
}
func Calc(c, d int) (sum, avg int) {
// return c + d, sum / 2 可以直接这样一句话,也可以向下面那样省略掉return后面的地方
sum = c + d
avg = sum / 2
return
}
func main() {
n := "Hikari"
a := "18"
Hello(n, a) // 18岁的Hikari向你问好!
fmt.Println(Calc(4, 8)) // 12 6
}
通过上面的例子我们可以发现,Go的函数有这样几个特点:
- 多个连续的参数类型相同时,可以只写一次类型返回值可以有多个给返回值命名后,return 后面不用再写东西
除此之外,还有如下特性
- 函数的参数可以不是固定的,但后面的类型是固定的
func myfunc(args ...int) { //0个或多个参数
}
func add(a int, args…int) int { //1个或多个参数
}
func add(a int, b int, args…int) int { //2个或多个参数
}
// 其中arg是一个slice,可以通过arg[index]依次访问所有参数
函数有多个返回值,那我调用函数只需要其中一个返回值怎么办?
_可以帮你解决这个问题,_能帮你忽略掉对应位置的返回值
还是看上面的那个Calc()函数
sum, _ := Calc(4,6) _, avg := Calc(4,6) fmt.Println(sum) // 10 fmt.Println(avg) // 5方法
// 方法
func (接收者变量 接收者类型) 方法名(参数列表)(返回参数){
函数体
}
// 函数
func 方法名(参数列表) (返回参数){
函数体
}
和函数对比一下,我们会发现,方法也就是在函数方法名前面加了个接收者
这个所谓接收者,可以理解成,只有指定的接收者类型才能调用这个方法
type Luna struct {
level int
have, awakening bool
}
type Eto struct {
level int
have, awakening bool
}
var e1 *Eto = &Eto{}
func main() {
l1 := &Luna{20, true, false}
l1.Can(true)
fmt.Println(e1.have)
// e1.Can() 这里会报错,未解析的引用 'Can'
}
func (l *Luna) Can(fc bool) { // 限制只有Luna的指针类型可以调用
fmt.Println("If you can !")
if !fc {
fmt.Println("No I can't...TAT")
} else {
fmt.Println("Character Unlocked!")
e1.have = true
e1.level = 1
}
}
// 运行结果
// If you can !
// Character Unlocked!
// true
就是这样,方法都在结构体外面,方法就像是限制了调用者的函数
方法有了,但总感觉还少了点啥,,,奥对,构造方法!
构造方法就拿爱托来做实验!
type Eto struct {
level int
have, awakening bool
}
func newEto(level int, have, awakening bool) *Eto {
return &Eto{
level: level,
have: have ,
awakening: awakening,
}
}
仔细观察会发现,这不就是一个函数,返回值是结构体的指针类型吗!
不过限制了我们必须初始化字段值罢了!



