11.指针
go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量。传递数据使用指针
而无需拷贝数据。
类型指针不能进行偏移和运算。
go语言中的指针操作非常简单,只需要记住两个符号:&(取地址)和*(根据地址取值,解引用)
指针地址和指针类型
每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。go语言中使用&字符放在变量签名对变量进行取地址操作。
go语言中的值类型(int,float,bool,string,array,struct)都有对应的指针类型,如:*int,*int64,*string等等。
指针语法
一个指针变量指向了一个值的内存地址。(也就是说我们声明了一个指针后,可以像变量赋值一样,把一个值的内存地址放入到指针当中)。
类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:
var var_name *var-type
//指针变量名 用于指定变量是作为一个指针 指针类型
指针声明实例
var ip *int//指向整型
var fp *float32//指向浮点型
指针使用实例
var ip *int//指针变量
var sp *string
var i int = 100//正常变量
var s string = "小明"
ip = &i//把正常变量的地址存到指针变量里
sp = &s
fmt.Println(*ip)//解引用输出指针变量里存的正常变量的地址的值
fmt.Println(*sp)
指向数组的指针
定义语法
var ptr [MAX]*int//表示数组里面的元素类型是指针类型
注意:
- 不能直接取数组的地址给数组指针变量
- 不能直接解引用存储数组地址的数组指针变量
- 应该循环存储数组每一个元素的地址到数组指针变量
- 循环的从数组指针变量里解引用得到的数组地址
实例演示
const MAX int = 3
a := [MAX]int{1, 2, 3}
var ap [MAX]*int
//ap=&a这样写是错误的
for i := 0; i < MAX; i++ {
ap[i] = &a[i]
}
fmt.Println(ap)
//fmt.Println(*ap)这样写是错误的
for _, v := range ap {
fmt.Println(*v)
}
12.结构体
go语言没有面向对象的概念了,但是可以使用结构体来实现,面向对象编程的一些特性,例如:继承,组合等特性。
go语言结构体的定义
结构体的定义和类型定义类似,只不过多了个struct关键词,语法结构:
type struct_variable_type struct{
member definition
member definition
...
}
type:结构体定义关键字
struct_variable_type:结构体类型名称
struct:结构体定义关键字
member definition:成员定义
go语言结构体定义实例
下面我们定义一个人的结构体Person
type Person struct{
id int
name string
age int
email string
}
以上我们定义一个Person结构体,有四个成员,来描述一个Person的信息
type Person struct{
id,age int
name,email string
}
声明一个结构体变量
声明一个结构体变量和声明一个普通变量相同,例如:
var tom Person
fmt.Println(tom){0,0}
kite:=Person{}
fmt.Println(kite)//{0,0}
访问结构体成员
可以使用点运算符(.),来访问结构体成员,例如:
func main(){
type Person struct{
id,age int
name,email string
}
var tom Person
tom.id=1
tom.name="tom"
tom.age=20
tom.email="tom@163.com"
fmt.Println(tom)//{1 20 tom tom tom@gmail.com}
}
匿名结构体
如果结构体是临时使用,可以不用起名字,直接使用,例如:
func main(){
var dog struct{
id int
name string
}
dog.id=1
dog.name="小狗"
fmt.Println(dog)
}
go语言结构体的初始化
未初始化的结构体,成员都是零值 int 0 float 0.0 bool false string nil
func main(){
type Person struct{
in,age int
name,email string
}
var tom Person
fmt.Println(tom)// 0 0 " " " "
}
使用键值对对结构体进行初始化
func main(){
type Person struct {
id,age int
name,email string
}
kite:= Person{
id: 1,
name:"kite",
age: 20,
email: "kite@gmail.com",
}
fmt.Println(kite)//{1,kite 20 kite@gmail.com}
}
使用值的列表初始化
func main(){
type Person struct{
id,age int
name,email string
}
kite:=Person{
1,
20,
"kite",
"kite@gmail.com"
}
fmt.Println(kite)//{1 20 kite kite@gmail.com}
}
注意:
- 必须初始化结构体的所有字段
- 初始值的填充顺序必须与字段的结构体中的声明顺序一直
- 该方式不能和键值初始化方式混用
部分成员初始化
用不到的成员,可以不进行初始化
func main(){
type Person struct{
id,age int
name,email string
}
kite:=Person{
id:1,
name:"kite"
}
fmt.Println(kite)//{1 0 kite " "}
}
go结构体指针
结构体指针和普通的变量指针相同,先来回顾一下普通变量的指针
func main(){
var name string
name = "tom"
//p_name 指针类型
var p_name *string
//&name 取name地址
p_name= &name
fmt.Println(name)//tom
//输出指针地址
fmt.Println(p_name)//0xcc00010
//输出指针指向的内容值
fmt.Println(*p_name)//tom
}
结构体指针
func main(){
type Person struct{
id int
name string
}
var tom = Person{1,"tom"}
var p_person Person
p_person = &tom
fmt.Println(tom)//{1,tom}
fmt.Println(p_person)//0xc000001313
fmt.Println(*p_person)//{1,tom}
}
使用new关键字创建结构体指针
我们还可以通过使用new关键词对结构体进行实例化,得到的是结构体的地址,例如:
func main(){
type Person struct{
id int
name string
}
var p_person = new(Person)
fmt.Println(p_person)//*main.Person
}
访问结构体指针成员
访问结构体指针成员,也使用点运算符(.),例如:
func main(){
type Person struct{
id int
name string
}
var p_person = new(Person)
fmt.Println(p_person)//*main.Person
p_person.id=1//等价于*p_person.id
p_person.name="tom"
fmt.Println(*p_person)//{1,tom}
}
go结构体作为函数参数
go结构体可以像普通变量一样,作为函数的参数,传递给参数,这里分为两种情况:
- 直接传递结构体,这是一个副本(拷贝),在函数内部不会改变外面结构体内容
- 传递结构体指针,这时在函数内部,能够改变外部结构体内容
直接传递结构体
实例
type Person struct{
id int
name string
}
func showPerson(person Person){
person.id=1
person.name="kite"
fmt.Println(person)
}
func main(){
person:=Person{1,"tom"}
fmt.Printf(person)//{1 tom}
fmt.Println("---------")
showPerson(person){1 kite}
fmt.Println("---------")
fmt.Println(person)//{1 tom}
}
从运行结果可以看出,函数内部结构了结构体内容,函数外面并没有被改变。
传递结构体指针
实例
type Person struct{
id int
name string
}
func showPerson(person *Person){
person.id=1
person.name = "kite"
fmt.Println(person)
}
func main(){
person:=Person{1,"tom"}
fmt.Println(person)//{1 tom}
fmt.Println("---------")
showPerson(&person)//{1 kite}
fmt.Println("---------")
fmt.Prinltn(person)//{1 kite}
}
从运行结果,我们可以看到,调用函数后,参数被改变了
go嵌套结构体
go语言没有面向对象编程思想,也没用继承关系,但是可以通过结构体嵌套来实现这种效果。
下面通过实例演示如何实现结构体嵌套,加入有一个人Person结构体,这个人还养了一个宠物Dog结构体。
下面我们来看一下:
Dog结构体
type Dog struct{
name string
color string
age int
}
Person结构体
type person struct{
dog Dog
name string
age int
}
访问它们
type Dog struct{
name string
color string
age int
}
type person struct{
dog Dog
name string
age int
}
func main(){
var tom person
tom.dog.name = "花花"
tom.dog.colo = "黑白花"
tom.dog.age = 2
tom.name = "tom"
tom.age = 20
fmt.Println(tom)//{{花花 黑白花 2} tom 20}
}
go方法
go语言没有面向对象的特性,也没有类对象的概念。但是,可以使用结构体来模拟这些特性,我们都知道面向对象里面有类方法等概念。
我们也可以声明一些方法,属于某个结构体。
go语言方法的语法
go中的方法,是一种特殊的函数,定义于struct之上(与struct关联,绑定),被称为struct的接收者(receiver)
通俗的讲,方法就是有接收者的函数。
语法的格式如下:
type mytype struct{}
func(recv mytype) my_method(para) return_type{}
func(recv *mytype) my_method(para) return_type{}
mytype:定义一个结构体
recv:接受该方法的结构体(receiver)
my_method:方法名称
para:参数列表
return_type:返回值类型
从语法格式看,一个方法和一个函数非常相似,多了一个接受类型
实例
struct Person struct{
name string
}
func (recv Person) login(name string) bool{
fmt.Println(recv.name)
if recv.name==name{
return true
}else{
return false
}
}
func (rec Person) userName(){
fmt.Println(rec.name)//tom
}
func main(){
var per= Person{"mike"}
b:=per.login("tom")
per.userName()//mike
fmt.Println(b)//false
}
go语言方法的注意事项
- 方法的receiver type并非一定要
strcut类型,type定义的类型别名,slice,map,channel,func等类型都可以。 struct结合它的方法等价于面向对象中的类。只不过struct可以和它的方法分开,并非一定要属于同一个文件,但必须属于同一个包。- 方法有两种接受类型:
(T Type)和(T *Type),两者有区别。 - 方法就是函数,所有go中没有方法重载(overload)的说法,也就是说同一个类型中的所有方法名必须都唯一。
- 如果receiver是一个指针类型,则会自动解除引用
- 方法和type是分开的,意味着实例的行为(behavior)和数据存储(field)是分开的,但是它们通过receiver建立起关联联系
方法接收者类型
结构体实例,有值类型和指针类型,那么方法的接收者是结构体,那么也有值类型和指针类型。区别就是接收者是非欧复制结构体副本。值类型复制,指针类型不复制。
值类型和指针类型结构体
type Person struct{
name string
}
func main(){
p1:=Person{"tom"}
fmt.Printf("%T",p1)//main.Person 值类型
p2:=&Person{"tom"}
fmt.Printf("%T",p2)//*main.Person 指针类型
}
方法的值类型和指针类型接收者
package main
import "fmt"
type Person struct {
name string
}
func (recv Person) showPerson() {
fmt.Printf("%p\n", &recv)
recv.name = "mike"
fmt.Println(recv)
}
func (recv *Person) showPersonTwo() {
fmt.Printf("%p\n", recv)
recv.name = "mike" //等价于*recv.name="mike"
fmt.Println(*recv)
}
func main() {
var p1 = Person{"tom"}
fmt.Printf("%p\n", &p1)
p1.showPerson()
fmt.Println(p1)
fmt.Println("-----------")
var p2 = &Person{"tom"}
fmt.Printf("%p\n", p2)
p2.showPersonTwo()
fmt.Println(*p2)
}
13.接口
go中的接口是一种抽象数据类型,go中接口定义了对象的行为规范,只定义规范不实现。接口中定义的规范由具体的对象来实现
通俗的讲接口就一个标准,它是对一个对象的行为和规范进行约定,约定实现接口的对象必须得按照接口的规范。
go接口的定义
- 在go中接口(interface)是一种类型,一种抽象的类型。接口(interface)是一组函数method的集合,go中的接口不能包含任何变量。
- 在go中接口中的所有方法都没有方法体,接口定义了一个对象的行为规范,只定义规范不实现。接口体现了程序设计的堕胎和高内聚低耦合的思想。
- go中的接口也是一种数据类型,不需要显示实现。只需要一个变量含有接口中的所有方法,那么这个变量就实现了这个接口
go中每个接口由数个方法组成,接口的定义格式如下:
type 接口名 interface{
方法1(参数列表1)返回值列表1
方法2(参数列表2)返回值列表2
...
}
其中:
- 接口名: 使用type将接口定义为自定义的类型名。go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型定义
- 方法名: 当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问
- 参数列表,返回值列表: 参数列表和返回值列表中的参数变量名可以省略
演示:定义一个Usber接口让Phone和Camera结构体实现这个接口
package main
import "fmt"
type Usber interface {
start()
stop()
}
//如果结构体里面有方法的话,必须要通过结构体或者通过自定义类型实现这个接口
type hone struct{}
type camera struct{}
//要实现usb接口的话必须要实现usb接口中的所有方法
func (p phone) start() {
fmt.Println("手机启动。。。")
}
func (p phone) stop() {
fmt.Println("手机关机。。。")
}
func (c camera) start() {
fmt.Println("相机启动。。。")
}
func (c camera) stop() {
fmt.Println("相机关机。。。")
}
func main() {
var p = phone{}
var c = camera{}
var p1 Usber //go中接口就是一个数据类型
var c1 Usber
c1 = c
p1 = p //表示手机实现usb接口
p1.start()
p1.stop()
c1.start()
c1.stop()
}
例题升级:
package main
import "fmt"
type Usber interface {
start()
stop()
}
//如果结构体里面有方法的话,必须要通过结构体或者通过自定义类型实现这个接口
type phone struct{}
type camera struct{}
type computer struct{}
//要实现usb接口的话必须要实现usb接口中的所有方法
func (p phone) start() {
fmt.Println("手机启动。。。")
}
func (p phone) stop() {
fmt.Println("手机关机。。。")
}
func (c camera) start() {
fmt.Println("相机启动。。。")
}
func (c camera) stop() {
fmt.Println("相机关机。。。")
}
func (cp computer) work(usb Usber) {//升级地方
usb.start()
usb.stop()
}
func main() {
var p = phone{}
var c = camera{}
var cp = computer{}
cp.work(p)
cp.work(c)
}
空接口
go中的接口可以不定义任何方法,没有定义任何方法的接口就是空接口。空接口表示没有任何约束,因此任何类型变量都可以实现空接口。
空接口在实际项目中用的是非常多的,用空接口可以表示任意数据类型。
案例1
type A interface{}
func main(){
var a A
var str = "你好golang"
a=str//让字符串实现A这个接口
fmt.Printf("值:%v 类型:%T",a,a)//值:你好golang 类型:string
}
案例2:go中空接口也可以直接当作类型来使用,可以表示任意类型
func main(){
var a interface{}
a=20
fmt.Printf("值:%v 类型:%T",a,a)//值:20 类型:int
a="你好golang"
fmt.Printf("值:%v 类型:%T",a,a)//值:你好golang 类型:string
a=true
fmt.Printf("值:%v 类型:%T",a,a)//值:true 类型:bool
}
案例3:空接口作为函数的参数
使用空接口实现可以接受任意类型的函数参数
func show(a interface{}){
fmt.Printf("值:%v 类型:%T\n",a,a)
}
func main(){
show(20)//值:20 类型:int
show("你好golang")//你好golang 类型:string
slice:=[]{1,2,3}//值:{1,2,3} 类型:slice
}
案例4:map的值实现空接口
使用空接口可以实现保存任意值的字典
func main(){
var m1 = make(map[string]interface{})
m1["username"]="张三"
m1["age"]=20
m1["married"]=false
fmt.Println(m1)//map[age:20 username:张三 married:true]
}
案例4:切片实现空接口
func main(){
var s1=[]interface{1,2,"你好",true}
fmt.Println(s1)//[1,2,"你好",true]
}
类型断言
一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。这两部分分别成为接口的动态类型和动态值
如果我们想要判断接口中值的类型,那么这个时候就可以使用类型断言,其语法格式:
x.(T)
- x:表示类型为interface{}的变量
- T:表示断言可能是的类型。
该语法返回两个参数,第一个参数是x转换为T类型后的变量,第二个值是一个布尔值,若为true则断言成功,为false则表示断言失败。
func main(){
var a interface{}
a="你好golang"
v,ok:=a.(string)
if ok{
fmt.Prinrln(v)
}else{
fmt.Println("断言失败")
}
}
定义一个方法,可以传入任意数据类型,然后根据不同的类型实现不同的功能
func MyPrint(x interface{}){
if_,ok:=x.(string);ok{
fmt.Println("string类型")
}else if_,ok:=x.(int);ok{
fmt.Println("int类型")
}else if_,ok:=x.(bool);ok{
fmt.Println("bool类型")
}
}
func main(){
MyPrintln("你好")
}
x.(type)判断一个变量的类型,这个语句只能用在switch语句里面
func MyPrint2(x interface{}){
switch x.(type){
case int:
fmt.Println("int类型")
case string:
fmt.Println("string类型")
case bool:
fmt.Println("bool类型")
default:
fmt.Println("啥也不是类型")
}
}
上文例子升级的升级
package main
import "fmt"
type Usber interface {
start()
stop()
}
//如果结构体里面有方法的话,必须要通过结构体或者通过自定义类型实现这个接口
type phone struct{}
type camera struct{}
type computer struct{}
//要实现usb接口的话必须要实现usb接口中的所有方法
func (p phone) start() {
fmt.Println("手机启动。。。")
}
func (p phone) stop() {
fmt.Println("手机关机。。。")
}
func (c camera) start() {
fmt.Println("相机启动。。。")
}
func (c camera) stop() {
fmt.Println("相机关机。。。")
}
func (cp computer) work(usb Usber) {//升级地方
//要判断usb的类型
if_,ok:=usb.(Phone);ok{//类型断言
usb.start()
}else{
usb.stop()
}
}
func main() {
var p = phone{}
var c = camera{}
var cp = computer{}
cp.work(p)
cp.work(c)
}
go中空接口和类型断言使用细节
type Address struct{
Name string
Phone int
}
func main(){
var userinfo = make(map[string]interface{})
userinfo["age"]=20
userinfo["hobby"]=[]string{"吃饭","睡觉"}
fmt.Println(userinfo["age"])//20
fmt.Println(userinfo["hobby"])//{吃饭 睡觉}
// fmt.Println(userinfo["hobby"][1])//interface{}类型不支持索引
var address =Address{
Name:"李四",
Phone:12345678910,
}
fmt.Println(address.Name)//李四
userinfo["address"] =address//userinfo map类型的 "address"键对应的值是address接口体
fmt.Println(userinfo["address"])//{李四 12345678910}
//var name = userinfo["address"].name//type interfaceP{}类型 是一个接口类型不支持这种方式去访问元素
hobby2,_:=userinfo["hobby"].([]string)//断言该键是不是字符串类型切片是的话就让断言后的类型交给hobbby2
fmt.Println(hobby2[0])//吃饭
address2,_:=userinfo["address"].(Address)
fmt.Println(address2.Name,address2.Phone)//李四 12345678910
}
结构体值接收者和指针接收者实现接口的区别
值接收者: 如果结构体中的方法是值接收者,那么实例化后的结构体值类型和结构体指针类型都可以赋值给接口变量
type Usber interface{
start()
}
type Phone struct{
name string
}
func (p Phone) start(){
p.name = "小米"
fmt.Println(p.name+"手机")
}
func main(){
var p = Phone{"华为"}
var p1 Usber = p
p.start()//小米手机
fmt.Println(p.name+"手机")//华为手机
var pp = Phone{"华为"}
var p2 Usber = &pp
p.start()//小米手机
fmt.Println(pp.name+"手机")//小米手机
}
指针接收者: 如果结构体中的方法是指针接收者,那么实例化后结构体指针类型都可以赋值给接口变量,结构体值类型没法赋值给接口变量。
type Usber interface{
start()
}
type Phone struct{
name string
}
func (p *Phone) start(){
p.name = "小米"
fmt.Println(p.name+"手机")
}
func main(){
var p = Phone{"华为"}
var p1 Usber = p
// p.start()//报错
//fmt.Println(p.name+"手机")
var pp =Phone{"华为"}
var p2 Usber = &pp
p.start()//小米手机
fmt.Println(pp.name+"手机")//小米手机
}
一个结构体实现多个接口(ocp ->Open Close Principle->"开闭原则")
go中一个结构体也可以实现多个接口 (对扩展是开放的,对修改是关闭的)
type Animal1 interface{
setName(string)
}
type Animal2 interface{
getName()string
}
type dog struct{
name string
}
func (d *dog) setName(name string){
d.name=name
}
func (d *dog) getName()string{
return d.name
}
func main(){
var d = &dog{name:"小胖"}
var a Animal1 = d
var b Animal2 = d
a.setName("小黑")
b.getName()
fmt.Printf("b.getName(): %v\n", b.getName())
}
接口嵌套
接口与接口间之间可以通过嵌套创造出新的接口
type A interface{
setName(string)
}
type B interface{
getName() string
}
type Animaler interface{
A
B
}
type Dog struct{
name string
}
func (d *Dog) setName(name string){
d.name = name
}
func (d Dog) getName() string{
return d.name
}
func main() {
var d = &Dog{name: "小胖"}
var a Animaler = d
a.setName("小黑")
fmt.Printf("b.getName(): %v\n", a.getName())
}
14.包
包有区分命名空间(一个文件夹中不能有两个同名文件),也可以更好的管理项目。
go中创建一个包,一般是创建一个文件夹在该文件夹里面的go文件中,使用package关键词声明包名称,
通常,文件夹名称和包名称相同。并且,同一个文件夹下面只有一个包。
创建包
- 创建一个
dao的文件夹 - 创建一个
dao.go文件 - 在该文件中声明包
package dao
import "fmt"
func Test1() {
fmt.Println("test package")
}
导入包
要使用某个包下面的变量或者方法,需要导入该包,在service.go中导入dao包
package service
import "dao"
func main(){
dao.Test1()
}
包注意事项
-
一个文件夹下只能由一个package
import后面是GOPATH/go mod开始的相对路径,包括最后一段。但由于一个目录下只能有一个package,所以import一个路径就等于import了这个路径下的包。- 注意,这里指的是"直接包含"的go文件。如果有子目录,子目录的父目录是完全两个包。
-
比如你实现了一个计算器package,名叫
calc,位于calc目录下;但又想给别人一个使用范例,于是在calc下可以建个example子目录(calc/example/),这个子目录里有个example.go(calc/example/example.go)。此时,example.go包可以是main包,里面还可以有个main函数。 -
一个package的文件不能在多个文件夹下
- 如果多个文件夹下有重名的package,它们其实是彼此无关的package。
- 如果一个go文件需要同时使用不同目录下的同名package,需要在
import这些目录时为每个目录指定一个package的别名
go包管理工具go module
go modules 是golang1.11新加的特性,用来管理模块中包的依赖关系。
go mod使用方法
-
初始化模块
go mod init <项目名称> -
依赖关系处理,根据
go.mod文件go mod tidy -
将依赖包复制到项目下的vendor目录
go mod vendor如果包被屏蔽(墙),可以使用这个命令,随后使用go build -mod=vendor编译
-
显示依赖关系
go list -m all -
显示详细依赖关系
go list -m -json all -
下载依赖
go mod download [path@version][path@version]是非必写的