指针
普通数据类型指针
- Go语言中的普通指针和C语言中的普通指针一样, 通过指针也可以间接操作指向的存储空间
- Go语言中指针的格式 :
var p *int
//1.普通指针
var num int = 10
var value float32 = 3.14
var flag bool = false
fmt.Println(num,value,flag)//10 3.14 false
//定义普通指针第一种方法
//var p1 *int = &num
//var p2 *float32 = &value
//var p3 *bool = &flag
//定义普通指针简写方法
p1 := &num
p2 := &value
p3 := &flag
//通过指针操作对应存储空间数据
*p1 = 20
*p2 = 6.66
*p3 = true
fmt.Println(num,value,flag)//20 6.66 true
指向数组的指针
- Go语言中指向数组的指针和C语言中指向数组的指针差不多, 通过指针也可以间接操作数组
- Go语言中指向数组指针的格式
var p *[3]int
- Go语言只要一个指针指向数组之后, 就可以通过指针来操作数组
- 注意点
- C语言中
数组名/&数组名/&数组首元素
都是同一个地址
Go语言中&数组名/&数组首元素
都是同一个地址, 但是通过数组名不能直接获取数组的地址
- 在Go语言中只有相同类型的数据才能赋值
- 在Go语言中指向数组的指针不支持+1 -1的操作
//2.数组的指针
var arr = [3]int{1,3,5}
//打印地址
fmt.Printf("%p\n",arr) //%!p([3]int=[1 3 5])
fmt.Printf("%p\n",&arr) //0xc04200e320
fmt.Printf("%p\n",&arr[0]) //0xc04200e320
//打印类型
fmt.Printf("%T\n", &arr)//*[3]int 数组类型指针
fmt.Printf("%T\n", &arr[0]) //*int int类型的指针
//定义数组的指针
var p *[3]int
p = &arr
//p = &arr[0] 不可以的,数据类型不同不能直接赋值
//通过指针操作数组
//第一种指针操作数组的写法
(*p)[0] = 666
fmt.Println(arr) //[666 3 5]
//第二种指针操作数组的写法
p[0] = 888
fmt.Println(arr) //[888 3 5]
指向切片的指针
- 一定要清楚切片的本质是一个结构体, 结构体中的一个属性是指针, 这个指针指向了底层的一个数组
-
注意点:
- 如果一个指针指向了数组, 那么可以通过
(*p)[0]
和p[0]
来操作指向的数组
- 但是如果一个指针指向了切片, 只能通过
(*p)[0]
来操作指向的切片
//切片的指针
var sce []int = []int{1,3,5,7}
//打印地址
//切片变量名sce打印的地址得到的是结构体中指向底层数组的指针保存的值
fmt.Printf("sce = %p\n",sce)//sce = 0xc04200e320
//&sce打印的地址是该切片自身的存储地址
fmt.Printf("&sce = %p\n",&sce)//&sce = 0xc042002440
//打印类型
fmt.Printf("%T\n", sce) //[]int
fmt.Printf("%T\n", &sce)//*[]int
//定义切片指针
var p *[]int
p = &sce
//结论: p == &sce *p == sce
fmt.Printf("%p\n", p) //0xc042002440
fmt.Printf("%p\n", *p) //0xc04200e320
//使用指针操作切片
//结论: 指针操作切片的方式只有一种(*切片指针名称)[索引]
(*p)[0] = 777
fmt.Println(sce)
//p[0] = 888 会报错,因为p == &sce,而操作切片需要sce来操作不是&sce
fmt.Println(sce)
指向字典的指针
- 和普通指针一样, 只能通过
(*p)[key]
来操作字典
//4.指向字典的指针
var dict = map[string]string{"name" :"wjh", "age" : "18"}
//定义字典类型的指针
var p *map[string]string = &dict
fmt.Printf("%T\n", p) //*map[string]string
fmt.Printf("%p\n",dict) //0xc04205c1e0
fmt.Printf("%p\n",*p) //0xc04205c1e0
//通过指针操作字典
(*p)["name"] = "lnj"
fmt.Println(dict) //map[name:lnj age:18]
指向结构体的指针
- 和C语言中指向结构体的指针一样, 可以通过
(*p).attr
和p.attr
来操作结构体
//5.结构体类型的指针
//定义结构体类型
type Person struct {
name string
age int
}
//定义结构体变量
per := Person{"wjh", 10}
//定义结构体指针变量
var p *Person
p = &per
fmt.Printf("%p\n", p) //0xc042002440
fmt.Printf("%p\n", &per) //0xc042002440
//使用指针操作结构体
//操作结构体的第一种方式
(*p).name = "zs"
//操作结构体的第二种方式
p.age = 30
fmt.Println(per)
指针作为函数的参数和返回值
- 指针作为函数的参数和C语言一样, 修改形参会影响实参
- 指针作为函数的返回值, 如果指针指向的是一个局部变量, 那么不建议返回
因为当函数调用完毕局部变量就释放了
方法
方法的概念和使用
- Go语言的方法就是一个特殊的函数, 函数是独立存在的, 而方法是和某种数据类型绑定在一起的
- 如何将一个函数和某种数据类型绑定在一起?
- 如下格式的含义就是将某个函数和接收者类型绑定在一起
func (接收者名称 接收者类型)函数名称(形参列表)(返回值列表){ 逻辑语句 }
- Go语言中的函数可以和任何类型绑定, 但是一般用于和结构体绑定
-
注意点
- 方法和函数的区别在于, 函数可以直接调用(通过包名.函数名称), 而方法只能通过绑定的数据类型对应的变量来调用(变量.函数名称)
- 函数名称和方法名称可以重名
package main
import "fmt"
//1.定义一个结构体
type Person struct {
name string
age int
}
//2.定义一个方法和这个结构体绑定
func (Person)say() {
fmt.Println("我是一个方法")
}
//3.定义一个方法指定接收者名称
//如果指定了接收者的名称, 那么调用方法时会将调用者传递给接收者
//简单理解: 可以把接收者也看做是函数的一个形参
func (per Person)say() {
fmt.Println("name = ", per.name, "age = ", per.age)
}
//4.既然可以把接收者看做是一个形参, 那么就存在值传递和地址传递问题
func (per Person)setName(name string) {
per.name = name
}
func (ptr *Person)setAge(age int) {
ptr.age = age
}
func main() {
/*
1.什么是Go语言的方法?
Go语言的方法就是一个特殊的函数, 函数是独立存在的, 而方法是和某种数据类型绑定在一起的
2.如何将一个函数和某种数据类型绑定在一起?
如下格式的含义就是将某个函数和接收者类型绑定在一起
func (接收者名称 接收者类型)函数名称(形参列表)(返回值列表){
逻辑语句
}
3.Go语言中的函数可以和任何类型绑定, 但是一般用于和结构体绑定
注意点:
1.方法和函数的区别在于, 函数可以直接调用(通过包名.函数名称), 而方法只能通过绑定的数据类型对应的变量来调用(变量.函数名称)
2.函数名称和方法名称可以重名
*/
//1.方法只能通过绑定数据类型的变量来调用
//定义结构体变量
per := Person{"wjh", 20}
//通过结构体变量调用方法
per.say()
//2.定义方法接收者名称
p := Person{"wjh", 20}
//通过结构体变量调用方法
p.say()
//3.接收者接收值类型和指针类型
p := Person{"wjh", 20}
//值传递类型,不能改变结构体的值
p.setName("lnj")
fmt.Println(p) //{wjh 20}
//地址传递,可以修改结构体属性的值
var pt *Person = &p
//调用方法有两种写法
pt.setAge(70)
(*pt).setAge(50)
fmt.Println(p)
}
方法和函数的异同
- 方法和函数都是
函数类型
- 函数通过
包名.函数名称()
调用 / 方法通过 变量.方法名称()
调用
- 方法的接收者可以看做是函数的一个形参, 会将调用者传递给接收者
接收者和函数形参一样, 也区分值传递和地址传递
到底是值传递还是地址传递, 和函数的形参一样
- 接收者地址传递的两种方式
(*指针变量名).方法名称()
普通变量名.方法名称()
- 只要接收者是指针类型, 那么编译器就会自动将普通变量的地址取出来传递给接收者
package main
import "fmt"
//1.定义一个结构体
type Person struct {
name string
age int
}
//2.定义方法绑定结构体
func (per Person)setName(name string) {
per.name = name
}
//3.接收者定义指针类型
func (ptr *Person)setAge(age int) {
ptr.age = age
}
//定义一个函数
func test() {
fmt.Println("我是一个函数")
}
func (Person)say() {
fmt.Println("我是一个方法")
}
func main() {
/*
1.方法和函数的异同
1.1方法和函数都是 函数类型
1.2函数通过 包名.函数名称() 调用 / 方法通过 变量.方法名称() 调用
1.3方法的接收者可以看做是函数的一个形参, 会将调用者传递给接收者
接收者和函数形参一样, 也区分值传递和地址传递
到底是值传递还是地址传递, 和函数的形参一样
接收者地址传递的两种方式
(*指针变量名).方法名称()
普通变量名.方法名称()
只要接收者是指针类型, 那么编译器就会自动将普通变量的地址取出来传递给接收者
*/
//1.定义结构体类型变量
var per Person= Person{"wjh", 20}
per.setName("lnj")
fmt.Println(per)
//只要接收者是指针类型, 那么编译器就会自动将普通变量的地址取出来传递给接收者
//接收者虽然是指针类型,但是也可以直接将普通变量传入
per.setAge(50)
fmt.Println(per)
//自己手动定义结构体类型指针变量
var ptr *Person
ptr = &per
(*ptr).setAge(100)
fmt.Println(per)
//2.方法和函数都是函数类型
var per Person= Person{"wjh", 20}
fmt.Printf("%T\n", test) //func()
fmt.Printf("%T\n", per.say) //func()
//既然方法也是函数,函数可以定义变量保存该函数,方法也可以
//定义一个用于保存没有形参没有返回值的函数类型变量
var fn = test
fn()
fn = per.say
fn()
}