Oop

面向对象

结构体(struct)

数组可以用来存储同一类型的数据,那多个不同项类型的数据我们就可以使用结构体。

结构体:是一种聚合数据类型,由零个或多个任意类型的值组合而成。

经典案例:

比如公司员工信息,每个员工信息都包含员工编号、名字、出生日期、岗位、家庭住址等。

再比如图书馆的书籍信息,每本书都包含标题、作者、学科等

在开发中,结构体是非常常用的一种类型。

定义

结构体的定义,使用关键词 type 和 struct 语句,如下定义:

// 声明 Book 结构体
type Book struct {
    Id     int
    Title  string
    Author string
}

func main() {

    // 方式一
    b1 := Book{}
    b1.Id = 1
    b1.Title = "MySQL 从入门到跑路"
    b1.Author = "BING"
    fmt.Printf("%#v\n", b1)
    //output: main.Book{Id:1, Title:"MySQL 从入门到跑路", Author:"BING"}
}
  • 成员属性名或结构体首字母大写,意味着可以导出其成员属性或该结构体
  • Printf 中 %#v 符号,意思是以 Go 语言语法格式的值输出
  • 结构体 b1 成员属性通过符号(.)访问

还可以声明时,直接为其赋值,如下:

b2 := Book{2, "PHP 从入门到放弃", "BING"}
fmt.Printf("%#v\n", b2)
//output: main.Book{Id:2, Title:"PHP 从入门到放弃", Author:"BING"}

b3 := Book{Id: 3, Title: "Go 语言"}
fmt.Printf("%#v\n", b3)
//output: main.Book{Id:3, Title:"Go 语言", Author:""}

以上的方式都是获得一个结构体实例,我们还可以获取一个结构体指针,如下两种方式:

b4 := new(Book)
b4.Id = 4
b4.Title = "江湖一声笑"
fmt.Printf("%#v\n", b4) // &main.Book{Id:4, Title:"江湖一声笑", Author:""}

// 等价于上面写法
b5 := &Book{Id: 5, Title: "江湖二声笑"}
fmt.Printf("%#v\n", b5) // &main.Book{Id:5, Title:"江湖二声笑", Author:""}

方法

Go 中没有面向对象,结构体中只有成员属性,接着我们看下如何实现一个成员方法,定义矩形结构体,并为其结构体绑定计算面积和计算周长两个方法,如下:

package main
import "fmt"

// 定义矩形结构体
type Rect struct {
    width, height float64
}

// 绑定计算面积方法
func (r *Rect) Area() float64 {
    return r.width * r.height
}

// 绑定计算周长方法
func (r Rect) Perimeter() float64 {
    return (r.height + r.width) * 2
}

func main() {
    r1 := Rect{width: 4.0, height: 5.0}
    // 计算面积
    fmt.Println(r1.Area()) // 20
    // 计算周长
    fmt.Println(r1.Perimeter()) // 18
}

注解:

  • 结构体 Rect 等同于面向对象的 Class
  • 结构体成员属性 width 和 height 等同于面向对象中的属性(property)
  • 结构体成员方法 Area() 和 Perimeter() 等同于面向对象语言中的方法(method)

细节:

  1. 结构体 Rect,首字母大写,意味着该结构体可以导出(即其他包内也可以访问)
  2. 成员属性 width 和 height 首字母我这里小写,等同于面向对象中的私有属性
  3. 成员方法 Area() 和 Perimeter() 采用首字母大写即可以导出,等同于面向对象中公开方法
  4. Area() 方法接收的是实例指针(*Rect),而 Perimeter() 接收则是实例对象本身,对于大的实例对象前者不会有性能开销,后者则会进行对象复制。

嵌套

多个结构之间,如果有相同的成员属性,可以通过结构体嵌套来复用代码,如下

// 圆形
type Circle struct {
    X, Y, Radius int
}

// 轮形
type Wheel struct {
    X, Y, Radius, Spokes int
}

不难看出,两个结构体共同含有成员属性 X、Y,且圆形成员属性是轮形的子集,那么就可以优化代码,如下写法:

type Point struct {
    X, Y int
}

// 圆形
type Circle struct {
    Center Point
    Radius int
}

// 轮形
type Wheel struct {
    Circle Circle
    Spokes int
}

那么如果想实例化一个 Wheel 实例,并为其成员属性赋值,那么写法就得如下:

func main() {
    var w Wheel
    w.Circle.Center.X = 8
    w.Circle.Center.Y = 8
    w.Circle.Radius = 5
    w.Spokes = 20
}

这深度是不是很酸爽,很容易懵逼,那么该咋闹呢?还有一种写法匿名嵌套,比如我们将嵌套的结构体定义匿名,如下:

type Point struct {
    X, Y int
}

// 圆形
type Circle struct {
    Point
    Radius int
}

// 轮形
type Wheel struct {
    Circle
    Spokes int
}

func main() {
    var w Wheel
    w.X = 8
    w.Y = 8
    w.Radius = 5
    w.Spokes = 20
}

这样访问成员属性是不是就简单了很多,是不是很开森,你都开森了是不是也给我个评论,鼓励我一下。

注意一点,结构体自己不能嵌套自身,但是可以嵌套自身的指针,如下定义一个树的结构体:

type tree struct {
    value       int
    left, right *tree
}

匿名结构体

上面讲到了结构体中使用匿名嵌套,接下来我们看下如何定义一个匿名结构体,匿名顾名思义即没有名字,定义如下:

func main() {
    p := struct {
        name string
        age  uint8
    }{
        name: "冰哥哥",
        age:  18,
    }
    fmt.Printf("%#v", p)
    //output: struct { name string; age uint8 }{name:"冰哥哥", age:0x12}
}