Golang结构体和接口

一、关于结构体

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())
}

二、结构体传参

2.1 指针

可以用new函数来创建一个结构体指针,如:p := new(Person),此时实际返回的数据类型是*Person,通过var p Person定义返回的数据类型为Person

2.2 结构体指针与传参

结构体也是传值的类型,有了指针,前面结构体方法定义与参数传递上则有一些变化的情况,如:

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 := new(Person)
	p.Name = "iTopic"
	p.Age = 18
	fmt.Println(p)
	fmt.Println(p.Info())
	fmt.Println(p)
}

//output:

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

和前面的区别可以看到

但这里有一个潜在的类型问题,既然p的类型为*Person,改为(p Person)为何方法不会报类型错误。同样,不使用new来创建*Person类型,也可以调用成功。

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

如果定义一个函数形参为p *Person,此时若传入非引用则会报类型错误:cannot use p (type Person) as type *Person in argument to test,可见正常情况下会认定为不同的类型。

func test(p *Person) {
	fmt.Println(p.Name)
}

但从实际情况上看,请求的两种方式:Person{"iTopic", 18}new(Person) 与 方法定义的两种方式(p Person)(p *Person),这四种情况组合起来都可以执行成功,唯一的区别点在于如果方法上是(p *Person)则实参会被改写,否则不会(传值与传址区别这里不表)。

三、结构体内嵌

3.1 基本嵌套

结构体是由多种数据类型组合起来的高级类型,自然结构体也可以嵌套结构体。按理所当然的去设置和访问就好了,比如:p.Job.CompanyName

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(),则可以这样子访问:

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

如果有多个这种继承字段也存在同名的方法,则就需要上面这种显示的指定了,否则会报:ambiguous selector p.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()
	}
}

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)
}
-- EOF --
发表于: 2020-05-12 20:00
标签: Golang