指针
普通指针
- 通过指针也可以间接操作指向的存储空间
格式:var p *int
var num int = 666
var value float32 = 3.14
var flag bool = false
var p1 *int
p1 = &num
fmt.Println(num)
*p1 = 678
fmt.Println(num)
var p2 *float32 = &value
fmt.Println(value)
*p2 = 6.66
fmt.Println(value)
p3 := &flag
fmt.Println(flag)
*p3 = true
fmt.Println(flag)
指向数组的指针
- 可以通过指针间接操作数组
格式:var p *[3]int
操作数组:(*p)[0] / p[0]
var arr [3]int = [3]int{1, 3, 5}
var p *[3]int
p = &arr
//p = &arr[0] //数据类型不同,不能赋值
(*p)[0] = 666
p[0] = 666
- 注意点:
- Go语言中 &数组名/&数组首元素 都是同意哦个地址,但是通过数组名不能直接获取数组的地址(C语言中可以)
- 只有相同类型的数据才能赋值
- 指向数组的指针不支持+1 -1操作
指向切片的指针
- 切片的本质是结构体,结构体的一个属性是指针,这个指针指向了底层的一个数组
操作切片:(*p)[0]
sce := []int{1, 3, 5}
//打印sce,得到的是结构体中指向底层数组的指针保存的值
//fmt.Printf("sce = %T\n",sce) // []int
//打印&sce,得到的是切片结构自己的地址
//fmt.Printf("&sce = %T\n",&sce) // *[]int
var p *[]int
p = &sce
//结论: p == &sce / *p == sce
(*p)[0] = 666
sce[0] = 678
p[0] =888 // 报错
指向字典的指针
操作字典: (*p)[key]
dict := map[string]string{"name":"yzf", "age":"18"}
var p *map[string]string
p = &dict
(*p)["name"] = "zx"
dict["name"] = "zs"
p["name"] = "ww" //报错
指向结构体的指针
- 和C语言一样
操作结构体: (*p).atter 和 p.atter
type Person struct {
name string
age int
}
var per Person = Person{"yzf", 18}
var p *Person
p = &per
(*p).name = "zs"
p.name = "zs"
指针作为函数的参数和返回值
- 地址传递
- 若指针指向的是一个局部变量,不建议返回(函数调用完后局部变量就释放)
方法
方法的基本使用
- 方法就是一个特殊的函数,函数独立存在,方法和某种数据类型绑定在一起的
- 格式:
func (接收者名称 接收者类型)函数名(形参列表)(返回值列表){
逻辑语句
}
- Go语言的函数可以和任何类型绑定,但一般用于和结构体绑定
- 注意:
- 方法和函数的区别:函数可以==直接调用(包名.函数名)==,方法只能==通过绑定的数据类型对应的变量来调用(变量.函数名)==
- 函数名和方法名可以重名
- 方法和函数都是函数类型
//1.定义一个结构体
type Person struct {
name string
age int
}
//2.定义一个方法,和Person结构体绑定
//如果指定了接收者的名称,那么调用方法时会将调用者传递给接收者
func (per Person)say() {
fmt.Println("name is", per.name, "age is", per.age)
}
3.接收者存在值传递和地址传递的问题
//值传递
func (per Person)setName(name string) {
per.name = name
}
//地址传递
//只要接收者是指针类型,编译器会自动将普通变量的地址取出来传递给接收者
func (per *Person)setAge(age int) {
(*per).age = age
}
func say() {
fmt.Println("hello")
}
p := Person{name:"yzf", age:18}
var ptr *Person = &p
//ptr.setName("zx")// 值传递
ptr.setAge(20)
p.say() //方法调用
say() //函数调用
接口
接口的基本使用
- 定义某种规范,只要使用者按照接口定义的标准来实现,无论谁实现都可以使用
- 作用:用于定义函数的声明(规定函数的形参,返回值,名称)
- 格式
type 接口名称 interface{
函数的声明
}
- 注意点:
- 接口只有方法的声明,没有方法的实现
- 接口中声明的方法,==只能通过和某种数据类型绑定的方法实现==,不能通过函数实现
- 只要某个数据类型实现了接口中声明的==所有==方法,就可以说这个数据类型实现了这个接口
- 一个数据类型实现了某个接口,就可以使用这个接口类型的变量来保存这个类型的数据,也可以使用接口类型变量调用接口中的方法
//1.定义一个接口
type USB interface {
//制定一个方法的标准
//名称是start,传入参数类型为string,没返回值
start(name string)
//名称是end,传入参数类型为string,没返回值
end(name string)
}
type Computer struct{
}
//实现接口中声明的方法
func (Computer)start(name string) {
fmt.Println(name, "启动了")
}
func (Computer)end(name string) {
fmt.Println(name, "关机了")
}
type Phone struct {
}
//实现接口中声明的方法
func (Phone)start(name string) {
fmt.Println(name, "启动了")
}
func (Phone)end(name string) {
fmt.Println(name, "关机了")
}
func main() {
cm := Computer{}
Option(cm, "DELL")
pp := Phone{}
Option(pp, "huawei")
}
//函数的作用,开关
func Option(in USB, name string) {
in.start(name)
in.end(name)
}
- ==重点掌握==:
1.定义一个接口
type USB interfa {
函数名(形参列表)(返回值列表)
函数名(形参列表)(返回值列表)
}
2.实现一个接口
只要某种数据类型绑定了所有接口中声明的方法就是实现了这个接口
3.实现接口后的特性
(1)只要某种数据类型实现了接口,那么就可以使用接口变量保存这种数据类型
(2)只要某种数据类型实现了接口,那么就可以使用接口变量调用接口中声明的方法
接口的注意点
- 接口中只能有方法的声明,==不能有方法的实现和变量的声明==
- 只有实现了接口中声明的所有方法,才算实现了接口,才算实现了接口,才能使用接口变量保存
- 实现接口时,方法名,形参列表,返回值必须一模一样
- 接口和结构体一样,可以嵌套(不能自己嵌套自己)
- 可以将超集接口变量赋值给子集接口变量,不可以讲子集接口变量赋值给超集接口变量(无论实际数据类型是否实现了超集的所有方法)
//子集接口
type aer interface {
// aer //会报错
start()
}
//超集接口
type ber interface {
aer //在ber中嵌套了aer
end()
}
type Phone struct {
name string
}
func (p Phone)start() {
fmt.Println(p.name, "启动了")
}
func (p Phone)end() {
fmt.Println(p.name, "关闭了")
}
func main() {
var b ber = Phone{"华为"}
var a aer
a = b //可以赋值
a.start()
/*
var a aer = Phone{"华为"}
var b ber
b = a //会报错
*/
}
- 接口中不能出现同名的方法声明
空接口
- Go语言中的空接口,相当于其他语言的Object类型
- Go语言中的空接口,可以充当任何类型
- 格式: interfa{}
- 有了空接口,可以让数组和字典保存不同类型的数据
- 注意:空接口类型的数据和普通的数据类型,在使用时有很大区别的
var value interface{}
value = 1 // 保存整型
value = 3.14 // 保存浮点类型
value = false // 保存布尔类型
value = 'T' // 保存字符
value = "lnj" // 保存字符串类型
value = [3]int{1, 3, 5} // 保存数组类型
value = []int{2, 4, 5} // 保存切片类型
value = map[string]string{"name": "lnj", "age": "18"} // 保存字典类型
value = Person{"lnj", 33} // 保存结构体类型
// 1.定义一个数组
var arr [3]interface{}
// 2.往数组中存储数据
arr[0] = 1
arr[1] = "lnj"
arr[2] = false
// 3.打印数据
fmt.Println(arr)
接口类型的转换
- 如果结构体实现了某个接口,那么就可以使用接口类型来保存结构体变量
- 如果利用接口类型变量保存了实现了接口的结构体,那么该变量只能访问接口中的方法,不能访问结构体中特有的方法,以及结构体中的属性
- 如果利用结构类型变量保存了实现了结构的结构体,想要访问结构体中特有的方法和属性,那么必须进行类型转换,将接口类型转换为结构体类型
type USB interface {
start()
}
type Computer struct {
name string
}
func (cm Computer)start() {
fmt.Println(cm.name, "启动了")
}
func (cm Computer)say() {
fmt.Println(cm.name)
}
func main() {
var in USB = Computer{"huawei"}
}
in.start()
//in.say() //报错
//fmt.Println(in.name) //报错
//类型转换
//方法一:使用类型断言
//格式: cm, ok := 接口类型变量名.(结构体类型)
cm, ok := in.(Computer)
fmt.Println(cm.name)
fmt.Println(ok)
//if cm, ok := in.(Computer); ok{
// cm.say()
// fmt.Println(cm.name)
//}
//方式二:使用类型断言
//会将接口类型变量转换为对应的原始类型之后赋值给cm
//格式:cm := 接口变量名.(type)
switch cm := in.(type) {
case Computer:
fmt.Println(cm)
cm.say()
default:
fmt.Println("不是Computer")
}
cm := Computer{"惠普"}
var sce []interface{} = []interface{}{1, 3.14, false, "lnj", cm}
// 需求: 获取切片中保存的每一个数据的原始类型
for key, value := range sce {
switch temp := value.(type) {
case int:
fmt.Println("第",key, "个元素是int类型")
case float64:
fmt.Println("第",key, "个元素是float64类型")
case bool:
fmt.Println("第",key, "个元素是bool类型")
case string:
fmt.Println("第",key, "个元素是string类型")
case Computer:
fmt.Println("第",key, "个元素是Computer类型")
temp.say()
}
//方法一除了可以将接口类型转换为具体类型以外,还可以将抽象接口类型转换为具体的接口类型
var in interface{}
in = Computer{"huawei"}
//in.start() //不可以利用抽象接口类型变量, 调用具体接口类型中声明的方法
if value, ok := in.(USB); ok{
value.start()
}
//或者
if value, ok := in.(Computer); ok{
value.start()
}
别名
- 在C语言中可以通过typedef给某种类型起一个别名
格式: typedef 原类型名 新类型名;
- 在Go语言中可以通过type给某种类型起一个别名
格式一: type 新类型名 原类型名
//代表起了一个新的类型叫 新类型名(是两个不同类型)
格式二: type 新类型名=原类型名
//代表给原类型名起了一个别名叫做 新类型名(是同一个类型)
//type newint int //报错
type newint = int
num1 := 666
var num2 newint
num2 = num1
fmt.Println(num1,num2)
网友评论