Golang ( 3 ) - 结构体和接口

一、关于结构体

1.1 基本定义

Go提供的结构体就是把使用各种数据类型定义不同变量组合起来高级数据类型。示例:

type Person struct {
	Name string
	Age  int
}

通过type关键字指定Person为一个结构体struct,并且包含2个属性NameAge,类型分别是stringint。所以说结构体可以把多种数据类型组合起来形成新的数据类型。 可以将Person结构体可以理解成定义了一个Person类,有两个成员属性NameAge,只是暂时还没有成员方法,接下来我们给结构体定义一个方法。

1.2 定义方法

给结构体定义了一个Info方法,返回一个string

type Person struct {
	Name string
	Age  int
}

func (p Person) Info() string {
	return fmt.Sprintf("Name: %s, Age: %d", p.Name, p.Age)
}

1.2 初始化

func main() {
	//方式一:先定义然后进行赋值
	var p Person
	p.Name = "iTopic"
	p.Age = 18

	//方式二:定义并初始化
	var q = Person{Name: "iTopic", Age: 18}

	//方法三:方法二相同,只不过这种写法最后一行需要保留逗号
	m := Person{
		Name: "iTopic",
		Age:  18,
	}

	//方式四:按字段顺序进行赋值就无需指定字段了,需要按顺序对每一个字段进行初始化
	n := Person{"iTopic", 18}

	fmt.Println(p, q, m, n)

	//调用结构体的Info方法
	fmt.Println(p.Info())
}

二、方法接收器

前面定义方法里使用的接收类型是Person,他等效于:func Info(p Person) string {},我们知道函数是可以传指针的,这里的接收方式也可以通过指针来接收。

2.1 指针接收器

前面结构体方法上也可以通过指针的方式来接收。

type Person struct {
	Name string
	Age  int
}

func (p *Person) Info() string {
	p.Age = p.Age + 1
	return fmt.Sprintf("Name: %s, Age: %d", p.Name, p.Age)
}

func main() {
	p := &Person{"iTopic", 18}
	fmt.Println(p)
	fmt.Println(p.Info())
	fmt.Println(p)
}

//output:

&{iTopic 18}
Name: iTopic, Age: 19
&{iTopic 19}

和前面的区别可以看到

2.2 结构体调用

这里一种有4种调用方式:

func (p *Person) Info() {
	fmt.Printf("Pointer: Name: %s, Age: %d\n", p.Name, p.Age)
}

func (p Person) Job() {
	fmt.Printf("Value: Name: %s, Age: %d\n", p.Name, p.Age)
}

func main() {
	//第一种情况:不通,报错 cannot call pointer method on Person{}
	//Person{}.Info()
	Person{}.Job()     // 输出:Value: Name: , Age: 0
	(&Person{}).Info() //输出:Pointer: Name: , Age: 0
	(&Person{}).Job()  //输出:Value: Name: , Age: 0
}

第一种方式报错,提示不能通过结构体值类型来调用指针方法。如果我们将初始化赋值给一个新的变量,则Go语言会帮我们做自动转换,都可以调用成功:

func main() {
	p1 := Person{}
	p2 := &Person{}
	p1.Info()
	p1.Job()
	p2.Info()
	p2.Job()
}

这里有一个示例,可以看看梳理下结构体的调用:

type Person struct {
	Name string
}

func (p *Person) Info() {
	fmt.Printf("Name: %s\n", p.Name)
}

func main() {
	p := []Person{{"A"}, {"B"}, {"C"}}
	for _, v := range p {
		go v.Info()
	}
	time.Sleep(3 * time.Second)
}

由于Person定义的是值类型,而调用的是指针接收的方法,所以Go的语法糖会帮我们做转换,直接运行的是go (*v).Info(),也就是取了v的地址,而range遍历过程中v用的是同一个变量地址。所以最后输出的是3个C,如果将Person的指针接收改成值接收的方式就可以顺利的输出ABC了。

三、结构体内嵌

3.1 基本嵌套

结构体是由多种数据类型组合起来的高级类型,自然结构体也可以嵌套结构体,这种用法就相当于定义了一个属性是一个复合类型。

type Person struct {
	Name string
	Age  int
	Job  Job
}

type Job struct {
	CompanyName string
}

3.2 内嵌匿名字段

我们去掉了Person中前面的Job变量名,同时给Job结构体增加了Show方法,可以看到:

Person获得了Job的属性和方法,这就像从Person类继承了Job类的成员属性和成员方法。

type Person struct {
	Name string
	Age  int
	Job
}

type Job struct {
	CompanyName string
}

func (p *Person) Info() string {
	p.Age = p.Age + 1
	return fmt.Sprintf("Name: %s, Age: %d", p.Name, p.Age)
}

func (j *Job) Show() string {
	return fmt.Sprintf("Job: %s", j.CompanyName)
}

func main() {
	var p Person
	p.Name = "iTopic"
	p.Age = 18
	p.CompanyName = "iTopic.org"
	fmt.Println(p, p.Info(), p.Show())
}

//output:
{iTopic 18 {iTopic.org}} Name: iTopic, Age: 19 Job: iTopic.org

如果存在同名的字段和方法则会优先获取到当前结构体的属性和方法,不存在才会去父结构体找。比如将Job.CompanyName改成Name,方法改成Info(),则可以按下面这样子访问,如果有多个这种继承字段也存在同名的方法,则就需要上面这种显示的指定了,否则会报:ambiguous selector p.Info

func main() {
	var p Person
	p.Name = "iTopic"
	p.Age = 18
	p.Job.Name = "iTopic.org"
	fmt.Println(p, p.Info(), p.Job.Info())
}

关于匿名嵌套这里有一点要补充,他其实并不是其它语言中的继承思路,Go本身是不支持继承,而是一种组合的思路。组合之后新的结构体有的老的结构体的方法和属性,但从访问层级上看还是满足原有的访问规则,并不是通过继承改变基类的属性和方法。

type Person struct {
	Name string
	Age  int
}

func (p *Person) Info() {
	fmt.Printf("Name: %s\n", p.Name)
}

type Student struct {
	Person
	Name string
}

func (s *Student) Detail() {
	fmt.Printf("Name: %s\n", s.Name)
	s.Info()
}

func main() {
	s := Student{}
	s.Name = "Lion"
	s.Detail()
}

示例中设置s.Name按优先级设置的是Student结构体的,而s.Info()中访问的p.NamePerson的,由于并没有对Person.Name赋值,所以s.Info()打印就是就初始值空字符串。

3.2 结构体锁

这里为前一章节的一个示例,比如对Age进行一千次调用累加操作,如果要确保数据准确就需要上锁,而sync.Mutex也是一个结构体,所以可以直接用内嵌的方式,Person就组合了Lock()Unlock()方法了。

type Person struct {
	Name string
	Age  int
	C    chan bool
	sync.Mutex
}

func (p *Person) Add() {
	p.Lock()
	defer p.Unlock()
	p.Age++
}

func main() {
	p := new(Person)
	p.Age = 18
	p.C = make(chan bool)
	for i := 0; i < 1000; i++ {
		go func(i int) {
			p.Add()
			p.C <- true
		}(i)
	}
	for i := 0; i < 1000; i++ {
		<-p.C
	}
	fmt.Println(p.Age)
}

四、关于接口

4.1 接口定义

接口是一组方法的集合。接口的定义,首先是关键字type,然后是接口名称,最后是关键字interface表示这个类型是接口类型。示例,

type ExportHandler interface {
	Export()
}

type CsvExporter struct {
}

func (e CsvExporter) Export() {
	fmt.Println("Export CVS File.")
}

type ExcelExporter struct {
}

func (e ExcelExporter) Export() {
	fmt.Println("Export Excel File.")
}

func main() {
	var handlerList = []ExportHandler{CsvExporter{}, ExcelExporter{}}

	for _, handle := range handlerList {
		handle.Export()
	}
}

注:不能声明一个接口的指针,比如函数使用*ExportHandler类型, 则会提示:

type *ExportHandler is pointer to interface, not interface

4.2 空接口

空接口就是不含有方法的接口:interface{},相当于所有的类型都实现了空接口,从示例可以看到类型都可以传给函数show而不会报类型错误。

func show(v interface{}) {
	fmt.Println(v)
}

func main() {
	show(123)
	show("123456")
	show([]int{1, 2, 3})
}

但上面的类型只是表示可以传给函数show, 而实际上无法做运算:

func show(v interface{}) {
	fmt.Println(v + 1)
}

func main() {
	show(123)
}
//invalid operation: v + 1 (mismatched types interface {} and int)

所以要做运算需要做断言:

func show(v interface{}) {
	switch val := v.(type) {
	case int:
		fmt.Println(val + 1)
	case string:
		fmt.Println(val + "123456")
	default:
		fmt.Println("unknown data type")
	}
}

func main() {
	show(1)
}

4.3 接口嵌套

接口也是支持内嵌的,通过嵌入其他接口就可以实现接口的组合。比如这是io包中的接口定义,ReadWriter接口通过组合的方式就相当于定义了两个方法。

type Reader interface {
	Read(p []byte) (n int, err error)
}

type Writer interface {
	Write(p []byte) (n int, err error)
}

type ReadWriter interface {
	Reader
	Writer
}

五、结构体与接口

5.1 结构体实现接口

前面接口定义中已经通过结构体来实现了接口,但如果想让某个结构体强制实现接口中的定义,可以这么做,相当于做了一个变量初始化,值做了类型强转。

var _ ExportHandler = (*CsvExporter)(nil)

5.2 结构体内嵌接口

内嵌接口就相当于给结构体多定义了几个方法,可以通过传入外部实现了的接口的结构体。

type Person struct {
	Name string
	Age  int
	Human
}

type Student struct {
	Name string
	Human
}

func (s Student) Info() {
	fmt.Printf("Name: %s\n", s.Name)
}

func main() {
	p := Person{Human: Student{Name: "ABC"}}
	p.Info()
}

-- EOF --
最后更新于: 2023-01-15 11:40
发表于: 2020-04-20 11:00
标签: Golang