【Go基础】结构体 | 青训营笔记

109 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

1. 结构体引入

Golang也支持面向对象编程,但是和传统的面向对象有区别,并不是像Java、C++那样纯粹的面向对象语言,而是通过特别的手段实现面向对象特点。

Golang没有类(Class)的概念,但是提供了结构体(struct),和其他编程语言中的类(class)有同等地位,可以理解为 :Go基于struct"拐弯抹角"的实现了面向对象的特点。

Golang面向对象编程非常简洁,去掉了传统OOP语言的 方法重载、构造函数、和析构函数、this、super一系列东西,但是保留了封装、继承、多态的特性,只是和其他语言的实现方式不同。

2. 结构体的定义

使用 type​ 和 struct​定义一个结构体

type People struct {
}

People就成了一个结构体,当然可以为People添加一些字段

type People struct {
    name string
    age string
    addr string
}

定义之后如何声明呢 ?

有以下几种方式 :

  1. 使用 var 变量名 结构体类型

    var people People
    people.name = "小明"
    people.age = 18
    fmt.Println(people) 
    // {小明 18}
    
  2. 变量名 := new(结构体类型)

    new返回的指向特定类型的指针,所以这种方式返回的是 指向一个People结构体 的指针

    people := new(People)
    people.name = "小明"
    people.age = 18
    fmt.Println(people) 
    // &{小明 18}
    

    为什么people是指针,还能使用 .​ 来访问结构体中的变量呢?使用 (*people).name 、(*people).age​当然可以访问,只是这种不好写,Go为我们进行了优化。

    如果第一种方式和这种方式让你选一种,你用哪一个呢?当一个结构体中的字段特别多时,直接创建该结构体会占用大量空间,但是指针不一样,指针固定大小。所以以上两种第二种常用。不过还有其他方式。

  3. 变量名 := 结构体类型{字段1:值, 字段1:值,}

    使用这种方式跟第一种差不多,都是直接开辟空间,不过这种方式可以直接对属性赋值

    (左边用var​还是:=​都一样)

    people := People{
        name: "小明",
        age: 18,
    }
    fmt.Printf("people的类型为: %T\n", people)
    // main.People// 这样也一样
    var people1 People = People{
        name: "小明",
        age: 18,
    }
    

    这可不是json,字段名不加双引号,最后一个字段后要要加逗号。

  4. 变量名 := &结构体类型{字段1:值, 字段2:值,}

    这种方式和第二种像,返回的是一个指向结构体的指针。

    people := &People {
        name: "小明",
        age: 18,
    }
    fmt.Printf("people的类型为: %T\n", people)
    //  *main.People
    

这四种方式选择哪一种,这要看习惯与需求,熟练用其中一种,可以看懂其他的方式就行。

初始化时可以直接列出全部的字段值,这种方式的缺点是 :无法指定字段,且一次性需要给全部字段赋值,如果后续由增加或删除,会出问题。

type People struct {
    name string
    age int
}
func main() {
    people := &People {
        "小明",
        18,
    }
}

匿名字段 :没有名字的字段。

type People struct {
    string
    int
}
func main() {
    people := &People {
        "小明",
        18
    }
​
}

赋值时需要严格按照字段类型与顺序,一般不用匿名字段,因为可读性太差,且如果出现多个string int类型,可读性更差

3. 结构体之间的转换

当两个不同类型的结构体中,字段数量字段名字段顺序字段类型全部相同时,这两个字段可以相互转换。

type People struct {
    name   string
    age    int
    height float32
}
​
type Monkey struct {
    name   string
    age    int
    height float32
}
​
func main() {
    // 声明一个People类型的结构体
    people := &People{
        name:   "小明",
        age:    18,
        height: 180.4,
    }
    // 将上述结构体变量转换为 *Monkey 类型
    monkey := (*Monkey)(people)
    fmt.Println(monkey)
    // 打印结果:  &{小明 18 180.4}
​
}

两个结构体是否能相互转换 :

字段数、字段名、字段顺序、字段类型​ 全部相同,缺一个就无法编译。

4. 结构体嵌套

说人话,结构体里还有结构体。

type Address struct {
    province string
    city     string
}
type People struct {
    id      int
    name    string
    age     int
    address Address
}
​
func main() {
    people := &People{
        id:   1,
        name: "小明",
        age:  18,
        address: Address{
            province: "河北",
            city:     "衡水",
        },
    }
    fmt.Println(people)
}

需要注意的是,People中的address字段如果是指针,在赋初值时就申请指针,是结构体就直接赋值结构体。

可以直接people.address.city访问嵌套结构体中的值.

fmt.Println(people.address.city)

这个经常使用,因为仅仅使用一个结构体无法描述出一个复杂的类型,就可以分为多个字段。

结构体中也可以有结构体数组,不过很少用这种方式一个一个new,都是从前端传来的json直接转换。

package main
​
import (
    "fmt"
    "strconv"
)
​
type Phone struct {
    name  string
    price int
}
​
func NewPhone(name string, price int) Phone {
    return Phone{
        name:  name,
        price: price,
    }
}
​
type People struct {
    name   string
    phones []Phone
}
​
func NewPeople(name string, phones []Phone) People {
    return People{
        name:   name,
        phones: phones,
    }
}
​
func main() {
    // 创建一个People数组
    people := NewPeople("小明", make([]Phone, 5, 200))
    // 循环给people数组中的值赋值
    for i := 0; i < 5; i++ {
        phoneName := "小米" + strconv.Itoa(i)
        phone := NewPhone(phoneName, 1000*(i+1))
        //
        people.phones[i] = phone
        // 如果在初始化切片时指定长度为0或者小于5,使用append()扩容
        // people.phones = append(people.phones, phone)
    }
    fmt.Println(people)
​
}

5. 方法的引入

方法是作用在指定的数据类型上、和指定的数据类型绑定,因此自定义类型都可以有方法,通常给结构体定义一些方法,以便外部调用,就像Java中的setter/getter方法、C++中的构造函数、析构函数一样。当然,Go语言中的方法并没有放在结构体中,而是和结构体分离。

形式:

func (变量名 : 自定义类型) 方法名(参数列表) (返回值列表) {

// 方法体

}

第一个括号中的变量不需要传入,而是*“谁调用,这个变量就是谁”*

当然可以不使用第一个变量。

给People定义一个初始化方法:

type People struct {
    name string
    age int
}
func newPeople(name string, age int) (*People) {
    return &People {
        people.name = name
        people.age = age
    }
}
func main() {
    people := newPeople("小明", 18)
}

这个newPeople方法接收两个变量,name和age,通过这两个变量创造一个Person对象并返回,以后再也不用频繁的写初始化方法了。

以下是使用第一个变量的例子 :

func (people *People) SetAge(age int) {
    people.age = age
}

这个方法的调用者必须是People类型的指针,否则会报错。

people := newPeople("小明", 18)
fmt.Println(people) // &{小明 18}
people.SetAge(10)
fmt.Println(people.age) // 10

就是方法的特殊性,让我们可以对结构体进行各种操作。你可以单独定义一个文件,在这个文件中完成对结构体的封装。

image

方法和函数的区别 :

  • 方法被调用者影响。
  • 函数与没有具体的调用者。