Go语言结构体、方法与接口

文章目录

一、结构体构造函数

Go语言中的构造函数:

  • Go 语言没有构造函数的功能
  • 使用结构体初始化的过程来模拟实现构造函数
  • 结构体可以像其他数据类型一样将结构体类型作为参数传递给函数

Go语言中的构造函数语法

go 复制代码
type 类型名 struct {
    字段名1 字段类型1
    字段名2 字段类型2
    ......
}
func newfunc(变量名1 变量类型, 变量名2 变量类型 ......) *类型名{
    return &类型名{
        字段名1: 变量1,
        字段名2: 变量2,
        ......
    }
}
  • Go语言的类型或结构体没有构造函数的功能,但是我们可以使用结构体初始化的过程来模拟实现构造函数。
  • 构造函数传入的变量类型 需要与return返回结构体指针字段名类型一致
  • 构造函数可以只返回结构体中某些字段名

示例1:

go 复制代码
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:

go 复制代码
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语言同时有函数和方法,方法的本质是函数 ,但是方法和函数又具有不同点。

无参数和返回值

go 复制代码
func (结构体变量 结构体类型) 方法名() {
    函数体
}

值类型接收者

go 复制代码
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}

指针类型接收者

go 复制代码
func (接收者变量 *接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}

示例:

go 复制代码
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的求取面积的方法。
  • 下面通过一个示例来观察不同的结构体中方法名。

示例:

go 复制代码
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也能调用该匿名结构体中的方法。
方法重写
  • 方法是可以继承和重写的。

示例:

go 复制代码
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()
}
go 复制代码
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:

go 复制代码
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:

go 复制代码
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:

go 复制代码
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:

go 复制代码
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语言中的接口支持赋值操作,从而快速实现接口与实现类的映射

接口格式

go 复制代码
type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
    ......
}

接口要点:

  • 接口类型名:使用 type 将接口定义为自定义的类型名,接口在命名时,一般会在单词后面添加er;
  • 方法名:当方法名首字母是大写时,同时接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问;
  • 参数列表:表示传入到方法中的值;
  • 返回值列表:方法返回值。
    示例:
go 复制代码
type Phone interface {
    call()
    SendMsg(msg string) bool
}

标准格式接口的实现:

go 复制代码
type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
    ......
}
type (变量名 类型) 方法名1( 参数列表1 ) 返回值列表1{
}
type (变量名 类型) 方法名2( 参数列表2 ) 返回值列表2{
}

示例:

go 复制代码
//标准格式接口的实现案列
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
}

空接口

  • 是接口类型的特殊形式
  • 空接口没有任何方法,因此任何类型都无须实现空接口
  • 从实现的角度看,任何值都满足这个接口的需求
  • 空接口类型可以保存任何值,也可以从空接口中取出原值

示例:

go 复制代码
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语言中的错误码,可逐层返回,直到被处理

示例:

go 复制代码
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

五、接口与结构体

接口与结构体的关系

一个结构体类型实现多个接口

格式:

go 复制代码
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{
}

示例:

go 复制代码
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

一个接口被多个结构体实现

格式:

go 复制代码
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{
}

示例:

go 复制代码
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 语言中,不仅仅结构体与结构体之间可以嵌套,接口与接口之间也可以嵌套,被包含的接口中的所有方法都会被包含到新的接口中。

接口嵌套的格式

go 复制代码
type 接口类型名1 interface{
    方法名1()
}
type 接口类型名2 interface{
    方法名2()
}
type 接口类型名3 interface{
    接口类型名1
    接口类型名2
    方法名3()
}

一个接口可以包含一个或多个其他的接口 ,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。只要接口的所有方法被实现,则这个接口中的所有嵌套接口的方法均可以被调用。

go 复制代码
//接口未嵌套
type queue interface {
    height()
}
type run interface {
    weight()
}
type BMI interface {
    height()
    weight()
    age()
}
go 复制代码
//接口嵌套
type queue interface {
    	height()
}
type run interface {
    	weight()
}
type BMI interface {
     	queue
	run
    	age()
}

示例:嵌套接口的实现

go 复制代码
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
相关推荐
blammmp24 分钟前
Java:数据结构-枚举
java·开发语言·数据结构
何曾参静谧36 分钟前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++
暗黑起源喵42 分钟前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong1 小时前
Java反射
java·开发语言·反射
Troc_wangpeng1 小时前
R language 关于二维平面直角坐标系的制作
开发语言·机器学习
努力的家伙是不讨厌的1 小时前
解析json导出csv或者直接入库
开发语言·python·json
Envyᥫᩣ1 小时前
C#语言:从入门到精通
开发语言·c#
齐 飞1 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
童先生1 小时前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
lulu_gh_yu1 小时前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法