Go语言结构体、方法与接口
文章目录
- 一、结构体构造函数
- Go语言中的构造函数语法
- 二、结构体方法和接收器
- 无参数和返回值
- 值类型接收者
- 指针类型接收者
- 方法继承
- 方法重写
- 三、结构体比较
- 结构体比较要求
- 结构体比较符号
- 四、接口声明
- 接口定义
- 接口特点
- 接口格式
- 标准格式接口的实现:
- 空接口
- error接口
- 五、接口与结构体
- 接口与结构体的关系
- 一个结构体类型实现多个接口
- 一个接口被多个结构体实现
- 六、接口的嵌套
- 接口嵌套的定义
- 接口嵌套的格式
一、结构体构造函数
Go语言中的构造函数:
- Go 语言没有构造函数的功能
- 使用结构体初始化的过程来模拟实现构造函数
- 结构体可以像其他数据类型一样将结构体类型作为参数传递给函数
Go语言中的构造函数语法
type 类型名 struct {
字段名1 字段类型1
字段名2 字段类型2
......
}
func newfunc(变量名1 变量类型, 变量名2 变量类型 ……) *类型名{
return &类型名{
字段名1: 变量1,
字段名2: 变量2,
......
}
}
- Go语言的类型或结构体没有构造函数的功能,但是我们可以使用结构体初始化的过程来模拟实现构造函数。
- 构造函数传入的变量类型需要与return返回结构体指针字段名类型一致
- 构造函数可以只返回结构体中某些字段名
示例1:
package main
import "fmt"
type person struct {
name string
age int
}
//构造函数约定俗成用new开头,不是强制
//返回的是结构体还是结构体指针
//当结构体比较大的时候使用结构体指针,减少程序内存的开销。
func personOnlyName(name string) *person {
return &person{
name: name,
}//仅返回部分字段
}
func newperson(name string, age int) *person {
return &person{
name: name,
age: age,
}//返回所有字段
}
func main() {
p1 := personOnlyName("Go语言")
fmt.Println(p1)
p2 := newperson("golang", 20)
fmt.Println(p2)
}
//运行结果:
//&{Go语言 0}
//&{golang 20}
示例2:
package main
import "fmt"
type person struct {
name string
age int
}
func print1(p person){
p.name="值传递"
fmt.Println("print1",p)
}
func print2(p *person){
p.name="指针传递"
fmt.Println("print2",p)
}
func main() {
p1:=person{"Go语言",20}
print1(p1) //值传递,形参无法改实参
fmt.Println(p1)
print2(&p1) //指针传递,形参可以改实参
fmt.Println(p1)
}
//运行结果:
//print1 {值传递 20}
//{Go语言 20}
//print2 &{指针传递 20}
//{指针传递 20}
二、结构体方法和接收器
Go语言同时有函数和方法,方法的本质是函数,但是方法和函数又具有不同点。
无参数和返回值
func (结构体变量 结构体类型) 方法名() {
函数体
}
值类型接收者
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
指针类型接收者
func (接收者变量 *接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
示例:
package main
import "fmt"
type person struct {
name string
age int
}
func (p person)print(){ //无参数和返回值
fmt.Println(p)
}
func (p person)change1(s string){
p.name=s //值类型接收者
}
func (p *person)change2(s string){
p.name=s //指针类型接收者
}
func main() {
p := person{"Go",20}
p.print()
p.change1("Golang")
p.print()
p.change2("Go语言")
p.print()
}
//运行结果为:
//{Go 20}
//{Go 20}
//{Go语言 20}
若方法的接受者不是指针,实际只是获取了一个copy,而不能真正改变接受者中原来的数据。
使用方法的原因:
- 既然可以用函数来写相同的程序,却还要使用方法,主要有以下两个原因
- 如Go不是一种纯粹面向对象的编程语言,它不支持类。因此其方法是一种实现类似于类的行为的方法。
- 如果相同名称的方法可以在不同的类型上定义,而具有相同名称的函数是不允许的。假设有一个正方形和圆形的结构。可以在正方形和圆形上定义一个名为Area的求取面积的方法。
- 下面通过一个示例来观察不同的结构体中方法名。
示例:
package main
import (
"math"
"fmt"
)
type Rectangle struct {
width, height float64
}
type Circle struct {
radius float64
}
func main() {
r1 := Rectangle{10, 4}
r2 := Rectangle{12, 5}
c1 := Circle{1}
c2 := Circle{10}
fmt.Println("r1 的面积", r1.Area())
fmt.Println("r2 的面积", r2.Area())
fmt.Println("c1 的面积", c1.Area())
fmt.Println("c2 的面积", c2.Area())
}
func (r Rectangle) Area() float64 {
return r.width * r.height
} // 定义 Rectangle 的方法
func (c Circle) Area() float64 {
return c.radius * c.radius * math.Pi
} // 定义 Circle 的方法
//运行结果为:r1 的面积 40
//r2 的面积 60
//c1 的面积 3.141592653589793
//c2 的面积 314.1592653589793
方法继承
- 方法是可以继承的,如果匿名字段实现了一个方法,那么包含这个匿名字段的struct也能调用该匿名结构体中的方法。
方法重写
- 方法是可以继承和重写的。
示例:
package main
import "fmt"
type Human struct {
name string
phone string
age int
}
type Student struct { // 继承Human
Human // 嵌入Human,匿名字段
school string
}
type Employee struct {
Human // 匿名字段
company string
}
// Human的SayHi方法
func (h Human) SayHi() {
fmt.Printf("大家好! 我是%s,%d岁,联系方式是: %s\n", h.name, h.age, h.phone)
}
func main() {
s1 := Student{Human
{"Daniel", "15012345678", 13}, "十一中学"}
e1 := Employee{Human
{"Steven", "17812345678", 35}, "1000phone"}
s1.SayHi()
e1.SayHi()
}
package main
import "fmt"
type Human struct {
name string
phone string
age int
}
type Student struct {
Human //继承Human
school string
}
type Employee struct {
Human //继承Human
company string
}
func (h Human) SayHi() {
fmt.Printf("大家好! 我是%s,%d岁,联系方式是: %s\n", h.name, h.age, h.phone)
}
func (s Student) SayHi() {
fmt.Printf("大家好! 我是%s,%d岁,我在%s上学,联系方式是: %s\n", s.name, s.age, s.school, s.phone)
}
func (e Employee) SayHi() {
fmt.Printf("大家好! 我是%s,%d岁,我在%s工作,联系方式是: %s\n", e.name, e.age, e.company, e.phone) }
func main() {
s1 := Student{Human{"Daniel", "15012345678", 13}, "十一中学"}
e1 := Employee{Human{"Steven", "17812345678", 35}, "1000phone"}
s1.SayHi() // 调用各自的SayHi方法
e1.SayHi()
}
三、结构体比较
结构体比较要求
- 结构体可以比较,也不可以比较
- 如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的
- 如果包含slice、map、function不可比较类型时,那么结构体是不可以比较的
结构体比较符号
- 可以比较的情况下:两个结构体将可以使用**==或!=运算符进行比较,但不支持>或<**
- 同类型的两个结构体变量可以相互赋值
示例1:
package main
import "fmt"
type person struct {
name string
age int
}
func main() {
p1 :=person{"Go语言",20}
p2 :=person{"Go语言",20}
if p1==p2{
fmt.Println("p1=p2")
}else {
fmt.Println("p1!=p2")
}
}
//运行结果为:
//p1=p2
示例2:
package main
import "fmt"
type person struct {
name string
age []int
}
func main() {
p1 :=person{"Go语言",[]int{20}}
p2 :=person{"Go语言",[]int{20}}
if p1==p2{
fmt.Println("p1=p2")
}else {
fmt.Println("p1!=p2")
}
}
//报错提示:
//invalid operation: p1 == p2 (struct containing []int cannot be compared)
!! 不能比较包含[]int的结构体
示例3:
package main
import "fmt"
type person1 struct {
name string
age int
}
type person2 struct {
name string
age int
}
func main() {
p1 :=person1{"Go语言",20}
p2 :=person2{"Go语言",20}
if p1==p2{
fmt.Println("p1=p2")
}else {
fmt.Println("p1!=p2")
}
}
//报错提示:
//invalid operation: p1 == p2 (mismatched types person1 and person2)
!! 结构体不同则不能比较
示例4:
package main
import "fmt"
type person1 struct {
name string
age int
}
type person2 struct {
name string
age int
}
func main() {
p1 :=person1{"Go语言",20}
p2 :=person2{"Go语言",20}
if p1==person1(p2){
fmt.Println("p1=p2")
}else {
fmt.Println("p1!=p2")
}
}
//运行结果:
//p1=p2
结构体内部相同时,可以使用强制转换来实现比较
四、接口声明
接口
- 面向对象语言中,接口用于定义对象的行为。接口只指定对象应该做什么,实现这种行为的方法(实现细节)是由对象来决定。
- 接口定义了一组方法,如果某个对象实现了该接口的所有方法,则此对象就实现了该接口。
Go语言是一种“鸭子类型”的语言
- Go没有 implements, extends 关键字,其实这种编程语言叫做duck typing编程语言。
- duck typing是描述事物的外部行为而非内部结构。在“鸭子类型”中,关注的不是对象的类型本身,而是它是如何使用的。
接口定义
- 接口是对类型行为的约定
- 接口是一系列方法的集合
- 接口是一种高度抽象的数据类型
- 接口中的方法不包含代码
- 接口中的方法是抽象的
- 接口中不包含变量
- 本质是一种关于对象功能的约定
接口特点
- 接口是双方约定的一种合作协议,接口实现者不需要关心接口会怎样被使用,只需要实现接口里面所有的方法即可
- 接口不支持直接实例化,只能通过具体的类来实现声明的所有方法,同时函数的函数名、函数参数和函数返回值必须完全一样
- Go语言中的接口支持赋值操作,从而快速实现接口与实现类的映射
接口格式
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
......
}
接口要点:
- 接口类型名:使用 type 将接口定义为自定义的类型名,接口在命名时,一般会在单词后面添加er;
- 方法名:当方法名首字母是大写时,同时接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问;
- 参数列表:表示传入到方法中的值;
- 返回值列表:方法返回值。
示例:
type Phone interface {
call()
SendMsg(msg string) bool
}
标准格式接口的实现:
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
......
}
type (变量名 类型) 方法名1( 参数列表1 ) 返回值列表1{
}
type (变量名 类型) 方法名2( 参数列表2 ) 返回值列表2{
}
示例:
//标准格式接口的实现案列
type AndroidPhone struct {
} //实现接口的结构体
func (a AndroidPhone) call() {
fmt.Println("AndroidPhone calling")
}
func (a AndroidPhone) SendMsg(msg string) bool {
fmt.Println("AndroidPhone sending msg")
return true
}
空接口
- 是接口类型的特殊形式
- 空接口没有任何方法,因此任何类型都无须实现空接口
- 从实现的角度看,任何值都满足这个接口的需求
- 空接口类型可以保存任何值,也可以从空接口中取出原值
示例:
package main
import "fmt"
func main() {
var any interface{}
any = 1
fmt.Println(any)
any = "hello"
fmt.Println(any)
any = false
fmt.Println(any)
}
//运行结果为:
//1
//hello
//false
error接口
- 作为错误处理的标准模式
- 如果函数要返回错误,则返回值类型列表中肯定包含 error
- error 处理过程类似于C语言中的错误码,可逐层返回,直到被处理
示例:
package main
import (
"errors"
"fmt"
"math"
)
func Sqrt(f float64) (float64, error) {
if f < 0 {
return -1, errors.New("开平方根的数字不能小于0")
}
return math.Sqrt(f), nil
}
func main() {
result, err := Sqrt(-13)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(result)
}
}
//运行结果为:
//开平方根的数字不能小于0
五、接口与结构体
接口与结构体的关系
一个结构体类型实现多个接口
格式:
type 接口类型名1 interface{
方法名1( 参数列表1 ) 返回值列表1
}
type 接口类型名2 interface{
方法名2( 参数列表2 ) 返回值列表2
}
type 结构体 struct{
字段名1 字段类型1
字段名2 字段类型2
}
type (结构体名 结构体) 方法名1( 参数列表1 ) 返回值列表1{
}
type (结构体名 结构体) 方法名2( 参数列表2 ) 返回值列表2{
}
示例:
package main
import "fmt"
type run interface{
running(time int)int
}
type eat interface{
eating(food string)int
}
type person struct {
weight int
}
func (p *person)running(time int)int{
p.weight -= time/5
return p.weight
}
func (p *person)eating(food string)int{
p.weight += 1
return p.weight
}
func main() {
p1 :=&person{
weight: 50,
}
fmt.Println(p1.weight)
p1.running(20)
fmt.Println(p1.weight)
p1.eating("面包")
fmt.Println(p1.weight)
//运行结果为:
//50
//46
//47
一个接口被多个结构体实现
格式:
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
}
type 结构体1 struct{
字段名1 字段类型1
}
type 结构体2 struct{
字段名2 字段类型2
}
type 结构体3 struct{
字段名3 字段类型3
}
type (结构体名1 结构体1) 方法名1( 参数列表1 ) 返回值列表1{
}
type (结构体名2 结构体2) 方法名2( 参数列表2 ) 返回值列表2{
}
type (结构体名3 结构体3) 方法名2( 参数列表2 ) 返回值列表2{
}
示例:
package main
import "fmt"
type who interface{
who(position string)string
}
type tearcher struct {
position string
name string
}
type student struct {
position string
name string
}
func (t *tearcher)who(name string)string{
t.position="教师"
t.name=name
return t.position
}
func (s *student)who(name string)string{
s.position="学生"
s.name=name
return s.position
}
func main() {
var t who=&tearcher{
position: "未知",
name: "未知",
}
t.who("Go")
fmt.Println(t)
var s who=&student{
position: "未知",
name: "未知",
}
s.who("go")
fmt.Println(s)
}
//运行结果为:
//&{教师 Go}
//&{学生 go}
六、接口的嵌套
接口嵌套的定义
在Go 语言中,不仅仅结构体与结构体之间可以嵌套,接口与接口之间也可以嵌套,被包含的接口中的所有方法都会被包含到新的接口中。
接口嵌套的格式
type 接口类型名1 interface{
方法名1()
}
type 接口类型名2 interface{
方法名2()
}
type 接口类型名3 interface{
接口类型名1
接口类型名2
方法名3()
}
一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。只要接口的所有方法被实现,则这个接口中的所有嵌套接口的方法均可以被调用。
//接口未嵌套
type queue interface {
height()
}
type run interface {
weight()
}
type BMI interface {
height()
weight()
age()
}
//接口嵌套
type queue interface {
height()
}
type run interface {
weight()
}
type BMI interface {
queue
run
age()
}
示例:嵌套接口的实现
package main
import "fmt"
type queue interface {
height() int
}
type run interface {
weight() int
}
type BMI interface {
queue //嵌套接口
run
age() int
}
type Person struct { //结构体
h, w, a int
}
func (p Person) height() int {
return p.h
} // 实现queue接口的height方法
func (p Person) weight() int {
return p.w
}
func (p Person) age() int {
return p.a
}
func main() {
p := Person{h: 170, w: 65, a: 25}
fmt.Println("Height:", p.height())
fmt.Println("Weight:", p.weight())
fmt.Println("Age:", p.age())
}
//运行结果为:
//Height: 170
//Weight: 65
//Age: 25