GO语言基础入门-结构体的使用 | 青训营笔记

128 阅读9分钟

前言

这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天,其实第五届青训营已经正式开课n多天了,笔记不断更新中,都是我听完课之后的总结和平时自己的学习积累,分享出来给有需要的朋友。

本文内容

本文承接上篇文章将涉及到Go语言结构体、继承以及接口的详细使用和细节讲解。

Go语言基础

1.Go语言结构体
(1)使用结构体的好处

Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。在Go语言中,结构体承担着面向对象语言中类的作用。

1.可以存取不同类型的值 数组和map只能存单一类型的数据

2.可以方便查询一组值

(2)创建结构体

使用结构体方式一,创建一个cat的变量

var cat1 Cat //var a int
cat1.Name = "小白"
cat1.Age = 3
cat1.Color = "白色"
fmt.Println("cat1=", cat1)
​
fmt.Println("name:", cat1.Name)
fmt.Println("age:", cat1.Age)
fmt.Println("color:", cat1.Color)
//使用slice时,一定要先make
cat1.slice = make([]int, 10)
cat1.slice[0] = 100//使用map,一定要先make
cat1.map1["key1"] = "tom"
fmt.Println(cat1)

使用结构体方式二

p2 := Person{
    "jack", 18,
}
fmt.Println(p2)
fmt.Println(p2.Name)

使用方式三

var p3 *Person = new(Person)
(*p3).Name = "jane"
p3.Name = "kangkang" //这个和上面是等效的
​
(*p3).Age = 30 //.的运算级别比*高,所以需要用括号包起来
p3.Age = 50
fmt.Println(*p3)

因为p3是一个指针,因此标准的给字段赋值方式(p3).Name ="jane" 也可以直接写成p3.Name="jane"。原因:go的设计者 为了程序员使用方便,底层会对p3.Name ="jane" 进行处理会给P3 加上取值运算( p3).Name ="jane"

使用方式4-{}

var person *Person = &Person{}
(*person).Name = "小敏"
(*person).Age = 18
//上面的语句也可以直接给字段赋值
// var person *Person=&Person{"小敏",18}
​
var aa int = 10
fmt.Printf("%p", &aa)

注意 结构体的方法调用时传的是值类型所以是值拷贝,如果传递的是地址,则是地址拷贝。

(3) GO语言OOP案例

编写一个Student结构体,包含name,gender,age,id,score字段,分别为string、string、int、int、float64类型。在结构体中声明一个say方法,返回string类型,方法返回信息中包含所有字段值。在main方法中,创建Student结构体实例(变量),并访问say方法,然后调用结果打印输出。

type Person struct {
    //结构体是值类型
    Name   string
    Gender string
    Age    int
    Id     int
    Score  float64
}
​
func (student *Person) say() string {
    info := fmt.Sprintf("student的信息 name:%v,gender:%v,age:%v,id:%v,score:%v",
        student.Name, student.Gender, student.Age, student.Id, student.Score)
    return info
}
​
func main() {
    //方式一
    //创建一个Student实例变量
    var stu = Person{
        Name:   "小美",
        Gender: "女",
        Age:    18,
        Id:     50,
        Score:  100.0,
    }
    //也可以直接下面这样写,要按照顺序写
    // var stu = Person{"小美", "女", 18, 50, 100.0}
​
    res := stu.say()
    fmt.Println(res)
    //方式二 返回结构体的指针类型
    var stu2 = &Person{
        Name:   "小美",
        Gender: "女",
        Age:    18,
        Id:     50,
        Score:  100.0,
    }
    fmt.Println("返回指针类型:", stu2)
    fmt.Println("指针数值:", *stu2)
}
(4)注意细节

在创建一个结构体变量后,如果没有给字段赋值,对应一个零值(默认值)

布尔Boolean类型是flase,数值是0,字符串是"",数组类型的默认值和它的元素类型相关,比如score[3]int则默认值为[0,0,0],包括指针,slice,和map的零值都是nil,即还没分配空间。

  • Golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说Golang支持面向对象编程特性是比较准确的
  • Golang没有类(Class),Go语言的结构体(struct)和其他编程语言的类型(class)有同样的地位
  • Golang面向对象编程非常简洁,去掉了传统OOP语言的继承、方法重载、构造函数和析构函数、隐藏的this指针等等
(5)工厂模式

如果模板包里的Student结构体是大写开头,创建要给Student实例

var stu = model.Student{
  Name: "tom",
  Age:  18.0,
}

如果定义的student首字母是小写,我们可以通过工厂模式解决

var stu = model.NewStudent("jack", 90.0, 100.0)
fmt.Println("指针:", stu)
fmt.Println("值", *stu)
fmt.Println("姓名:", (*stu).Name)
fmt.Println("分数:", (*stu).GetScore())
2.Go语言继承
(1)实现方式

在go中使用继承匿名结构体的方式实现来实现继承。

(2)示例
type Student struct {
    Name  string
    Age   int
    Score int
}
​
// 将Pupil 和 Graduate 共有的方法也绑定到 *Student
func (stu *Student) ShowInfo() {
    fmt.Printf("学生名:%v,年龄:%v,成绩:%v\n", stu.Name, stu.Age, stu.Score)
}
​
func (stu *Student) SetScore(score int) {
    // 输入分数 两者都可以直接使用
    stu.Score = score
}
​
func (stu *Student) Getsum(n1 int, n2 int) int {
    // 求和 两者都可以直接使用
    return n1 + n2
}
​
// 小学生
type Pupil struct {
    Student //嵌入了Student 匿名结构体
    // 如果嵌入的结构体和本结构体有相同方法或者字段时,编译器则采取就近原则访问、
    //如希望访问匿名结构体的字段和方法,则可以通过匿名结构体名来区分
}
​
// 小学生考试 Pupil结构体特有的方法,保留
func (p *Pupil) testing() {
    fmt.Println("小学生考试中...")
}
​
// 大学生
type Graduate struct {
    Student //嵌入了Student 匿名结构体
    // 如果嵌入的结构体和本结构体有相同方法或者字段时,编译器则采取就近原则访问、
    //如希望访问匿名结构体的字段和方法,则可以通过匿名结构体名来区分
}
​
// 大学生考试 Pupil结构体特有的方法,保留
func (p *Graduate) testing() {
    fmt.Println("大学生考试中...")
}
​
func main() {
​
    Pupil := &Pupil{}
    Pupil.Student.Name = "小学生明明"
    Pupil.Student.Age = 10
    Pupil.Student.Score = 80
    fmt.Println(*Pupil)
    Pupil.testing()
    Pupil.ShowInfo()
    res := Pupil.Getsum(10, 20)
    fmt.Println("两数之和是:", res)
    fmt.Println()
​
    Graduate := &Graduate{}
    Graduate.Student.Name = "大学生康康"
    Graduate.Student.Age = 22
    Graduate.Student.Score = 100
    fmt.Println(*Graduate)
    Graduate.testing()
    Graduate.ShowInfo()
    res2 := Graduate.Getsum(50, 50)
    fmt.Println("两数之和是:", res2)
}
(3)多重继承概念

重点1

如果结构体C包含A和B,C没有Name字段,而A和B有Name,这时必须通过指定匿名结构体名字来区分,否则C.Name 会报编译错误,应该使用c.A.Name="tom"z具体指明才不会报错,如果方法有相同的冲突也是一样的道理

重点2

如果D中结构体是有名结构体,比如下面这样

type D struct {
  a A //组合关系 ,有名结构体
}
var b D

那么当我们访问有名结构体时,就不能通过b.Name,必须带上结构体名

因为A是有名结构体,所以只能通过b.a.Name访问

重点3

在嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值

//方法一
tv:=TV{ Goods{"电视剧01":5500},Brand{"电视剧02":4999},}
​
//方法二
 tv:=TV{
    Goods{
        Name:"电视剧01",
        Price:5500
    },
    Brand{
        Name:"电视剧02",
        Price:4999
    },
    }

重点4

结构体的匿名字段是基本数据类型,也是可以正常使用的,比如下面的int

type A struct{
    Name string
    Age int
}
type Stu struct{
    A int  //匿名字段是基本数据类型
    n int
}
func main () {
    stu:Stu{}
    stu.Name="tom"
    stu.Age=10
    stu.int=80
 stu.n.int=50
    fmt.Println(stu)
}

但是无论是否超过容量,你都得把值重新赋值给原变量,不然即使修改同地址,但原切片还是没变化。

3.Go语言接口
(1)接口基本介绍

1.接口(interface)里所有方法都没有方法体,即接口的方法都是没有实现的方法。接口提现了程序设计的多态和高内聚低耦合的思想

2.Golang中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang中没有implement这样的关键字。

(2)接口的定义和使用
// 让手机实现Usb接口的方法
func (c Phone) Start() {
    fmt.Println("手机开始工作...")
}
​
// 让手机实现Usb接口的方法
func (c Phone) Stop() {
    fmt.Println("手机停止工作...")
}
​
// 定义一个接口中没有的方法(手机特有的方法)
func (c Phone) Call() {
    fmt.Println("手机在打电话...")
}
​
type Camera struct {
    Name string
}
​
// 让相机实现Usb接口的方法
func (c Camera) Start() {
    fmt.Println("相机开始工作...")
}
​
// 让相机实现Usb接口的方法
func (c Camera) Stop() {
    fmt.Println("相机停止工作...")
}
​
// 计算机
type Computer struct {
}
​
//编写一个working方法,接收一个Usb接口类型变量
//只要是实现了Usb接口 (所谓实现Usb接口,就是指实现了Usb接口声明的所有方法)
​
func (c Computer) Working(usb Usb) {
    usb.Start()
​
    //如果usb指向的是Phone结构体变量,则还需要调用Call方法
    //但是相机不能打电话,所以我们要做个类型断言(判断)
    if phone, flag := usb.(Phone); flag {
        phone.Call() //也就是说只有传入的是Phone是才会调用该方法
    }
    usb.Stop()
​
}
​
func main() {
    //测试
    //先创建结构体变量
    computer := Computer{}
    phone := Phone{}
    camera := Camera{}
​
    //关键点
    computer.Working(phone)
    computer.Working(camera)
​
    /*多态数组*/
    //定义一个Usb接口数组,可以存放Phone和Camera的结构体变量
    //这里就体现了多态数组
    var usbArr [3]Usb
    usbArr[0] = Phone{"vivo"}
    usbArr[1] = Phone{"小米"}
    usbArr[2] = Camera{"尼康"}
    fmt.Println(usbArr)
​
}
(3)接口使用的注意事项与细节

1.接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型变量(实例)

2.接口中所有的方法都没有方法体,即都是没有实现的方法

3.在Golang中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了这个接口

4.一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型

5.只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型,非结构体类型也可以实现接口。

// 1.实例
type Stu struct {
    Name string
}
​
func (stu Stu) say() {
    fmt.Println("Stu say()...")
}
// 5实例
type Integer intfunc (i Integer) say() {
    fmt.Println("integer Say() i =", i)
}
​
type Myinterface interface {
    say()
}
​

总结

1.如只是读取结构体里成员,不牵涉修改,可以作为参数的值传递:变量名 结构体类型

2.如要修改结构体里成员,就要用结构体指针传入:变量名 *结构体类型

3.近乎100%的使用都采用“传址”的方式,将结构体的引用传递给所需函数。

写在最后

本文是我的日常学习笔记,如果哪里有写错,麻烦请指出来,感谢。这里我也推荐大家多写笔记,写笔记是一个很好的习惯,可以帮助我们更好的吸收和理解学习的新知识,新的一年大家一起加油!