GO,GO,GO 向前进。。。

之前的类型如整型、字符串等只能描述单一的对象
如果是聚合对象,就无法描述了
想描述它就需要使用结构体

结构体

结构体是一种聚合类型,里面可以包含任意类型的值
这些值就是我们定义的结构体的成员,也称为字段

1. 定义结构体

定义一个结构体需要使用 type 和 struct 关键字

type structName struct {
	fieldName typeName
	......
	......
}
  • type 和 struct 是Go语言的关键字
    二者组合就代表要定义一个新的结构体类型
  • structName 是结构体类型的名字
  • fieldName 是结构体的字段名,而 typeName 是对应的字段类型
  • 字段可以是零个、一个或者多个

2. 结构体声明使用

// 定义结构体
type person struct {
	name string
	age uint
}

func main() {
	// 直接声明,这里p里面的值就是默认零值
	var t person
	t.name = "zhouxiaoxi"
	t.age = 3
	fmt.Println(t.name, t.age)
	// 使用结构体字面量的值进行初始化
	// 字面量的值必须和结构体的值的类型相同
	p := person{"zhouzhaodong", 31}
	fmt.Println(p.name, p.age)
	// 可以通过如下方式初始化,当然顺序也是可以打乱的。
	m := person{age: 31, name: "xiaoqiang"}
	fmt.Println(m.name, m.age)
}

运行结果为:
image.png

接口

接口是和调用方的一种约定
它是一个高度抽象的类型
不用和具体的实现细节绑定在一起

1. 接口实现

package main

import (
	"fmt"
)

type person struct {
	name string
	age  uint
	addr	address
}

type address struct {
	provice string
	city    string
}

// 定义接口
type Stringer interface {
	// 获取一个字符串
	String() string
}

// person类型实现String()接口
func (p person) String() string {
	return fmt.Sprintf("this name is %s, age is %d, provie is %s, city is %s", p.name, p.age, p.addr.provice, p.addr.city)
}

// address类型实现String()接口
func (a address) String() string {
	return fmt.Sprintf("this address is %s, %s", a.provice, a.city)
}

// 面向接口编程
// 只要一个类型实现了String()接口,都可以打印出对应的字符串
func printString(s Stringer) {
	fmt.Println(s.String())
}

func main() {
	p := person{"zhouzhaodong", 31, address{"山东", "青岛"}}
	printString(p)
	a := p.addr
	printString(a)
}

运行结果为:
image.png

注意:如果一个接口有多个方法,那么需要实现接口的每个方法才算是实现了这个接口。

2. 值接收者和指针接收者

二者都可以调用方法
因为Go语言编译器自动做了转换
所以值类型接收者和指针类型接收者是等价的

// 将变量p的指针作为实参传给printString参数也是可以的
printString(&p)

以值类型接收者实现接口的时候
类型本身该类型的指针类型都实现了该接口

// person指针类型实现String()接口
func (p *person) String() string {
	return fmt.Sprintf("this name is %s, age is %d, provie is %s, city is %s", p.name, p.age, p.addr.provice, p.addr.city)
}

func main() {
	p := person{"zhouzhaodong", 31, address{"山东", "青岛"}}
	// 这里编译不通过,报错如下所示
	printString(p)
}

意思就是类型person没有实现Stringer接口,这就证明了以指针类型接收者实现接口的时候只有对应的指针类型才被认为实现了该接口
image.png

当值类型作为接收者时,值类型和指针类型都实现了该接口
当指针类型作为接收者时,只有指针类型实现了该接口

方法接收者实现接口的类型
(p person)person 和 *person
(p *person)*person

3. 工厂函数

工厂函数一般用于创建自定义的结构体,便于使用者调用,我们还是以 person 类型为例,用如下代码进行定义:

package main

import (
	"fmt"
)

type person struct {
	name string
	age  uint
	addr	address
}

type address struct {
	provice string
	city    string
}

// 定义一个工厂函数
func NewPerson(name string) *person{
	return &person{name: name}
}

func main() {
	// 通过工厂函数创建自定义结构体的方式,可以让调用者不用太关注结构体内部的字段,只需要给工厂函数传参就可以了。
	fmt.Println(NewPerson("小强"))
}

运行结果为:
image.png

因为这里只是对name进行赋值了,所以其余的值就是类型的默认零值。

package main

import (
	"fmt"
)

//结构体,内部一个字段s,存储错误信息
type errorString struct {
	s string
}

//用于实现error接口
func (e *errorString) Error() string {
	return e.s
}

//工厂函数,返回一个error接口,其实具体实现是*errorString
func New(text string) error {
	return &errorString{text}
}

func main() {
	fmt.Println(New("错误"))
}

运行结果为:
image.png

其中,errorString 是一个结构体类型,它实现了 error 接口,所以可以通过 New 工厂函数,创建一个 *errorString 类型,通过接口 error 返回。

这就是面向接口的编程,假设重构代码,哪怕换一个其他结构体实现 error 接口,对调用者也没有影响,因为接口没变。

4. 继承和组合

在 Go 语言中没有继承的概念,所以结构、接口之间也没有父子关系,Go 语言提倡的是组合,利用组合达到代码复用的目的,这也更灵活。

package main

// 定义一个Person结构体
type Person struct {
}

func (p *Person) Say() {
	println("I'm a person.")
}

// 定义一个Student结构体
type Student struct {
	// 组合(将Person的Say整合)
	Person
}

func (s *Student) Say1() {
	println("I'm a student.")
}

func main() {
	// 通过组合Student就有两个方法
	var s Student
	s.Say()
	s.Say1()
}

运行结果为:
image.png

// 定义一个person结构体
// 因为 person 组合了 address,所以 address 的字段就像 person 自己的一样,可以直接使用。
type person struct {
	name string
	age  uint
	//组合address结构体
	addr	address
}

//定义一个address结构体
type address struct {
	provice string
	city    string
}

类型组合后,外部类型不仅可以使用内部类型的字段,也可以使用内部类型的方法,就像使用自己的方法一样。如果外部类型定义了和内部类型同样的方法,那么外部类型的会覆盖内部类型,这就是方法的覆写。

5. 类型断言

有了接口和实现接口的类型,就会有类型断言。类型断言用来判断一个接口的值是否是实现该接口的某个具体类型。

package main

import "fmt"

type person struct {
	name string
	age  uint
	addr address
}

type address struct {
	provice string
	city    string
}

func (p person) String() string {
	return fmt.Sprintf("this name is %s, age is %d", p.name, p.age)
}

func (a address) String() string {
	return fmt.Sprintf("this provice is %s, city is %s", a.provice, a.city)
}

func main() {
	var s fmt.Stringer
	p1 := person{name: "xiaohong", age: 25}
	s = p1
	// 这里判断s是否为person类型
	p2 := s.(person)
	fmt.Println(p2)

	a1 := address{"山东省", "青岛市"}
	s = a1
	// 这里判断s是否为person类型
	a2, ok := s.(person)
	if ok {
		fmt.Println(a2)
	} else {
		fmt.Println("s不是一个person")
	}
}

运行结果为:
image.png

小练习

实现有两个方法的接口

package main

import "fmt"

// 定义一个person实体类
type person struct {
	name string
	age  uint
}

type WalkRun interface {
	// 实现两个方法
	Run()
	Walk()
}

func (p person) Run() {
	fmt.Printf("%s, 会跑\n", p.name)
}

func (p person) Walk() {
	fmt.Printf("%s, 会走\n", p.name)
}

func main() {
	
	var w WalkRun
	a := person{"小强", 12}
	// 关键是这里,将a赋值给w
	w = a
	w.Run()
	w.Walk()
	// 重新定义一个person赋值给w
	b := person{"小红", 14}
	w = b
	w.Run()
	w.Walk()
}

运行结果为:
image.png

这里知识点有点多,需要仔细消化一下!!!

上一篇 下一篇