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
相关推荐
sycmancia几秒前
C++——强制类型转化、const的理解
开发语言·c++
hzb666661 分钟前
unictf2026
开发语言·javascript·安全·web安全·php
燃于AC之乐2 分钟前
深入解剖STL deque:从源码剖析到容器适配器实现
开发语言·c++·stl·源码剖析·容器实现
kaikaile19953 分钟前
基于MATLAB的滑动轴承弹流润滑仿真程序实现
开发语言·matlab
禹凕5 分钟前
Python编程——进阶知识(MYSQL引导入门)
开发语言·python·mysql
Victor3565 分钟前
MongoDB(2)MongoDB与传统关系型数据库的主要区别是什么?
后端
JaguarJack6 分钟前
PHP 应用遭遇 DDoS 攻击时会发生什么 从入门到进阶的防护指南
后端·php·服务端
BingoGo6 分钟前
PHP 应用遭遇 DDoS 攻击时会发生什么 从入门到进阶的防护指南
后端
Victor3567 分钟前
MongoDB(3)什么是文档(Document)?
后端
傻乐u兔1 小时前
C语言进阶————指针4
c语言·开发语言