更新系列
Go语言头秃之路(零)
Go语言头秃之路(一)
Go语言头秃之路(二)
Go语言头秃之路(三)
Go语言头秃之路(五)
Go语言头秃之路(六)
Go语言头秃之路(七)
-
面向对象(结构体)
- 结构体:自定义数据类型
结构体是值类型。
定义:
type 结构体名 struct { 属性名1 类型1 属性名2 类型2 }
结构体四种实例创建方式:
type Stu struct { Name string Age int } // 1. var s1 Stu // 2. s2 := Stu{"tony", 10} s2_2 := &Stu{"tony", 10} // 实例化为指针类型变量,存的是地址,多用于工厂模式 // 3. 这里s3.Name == (*s3).Name,go编译器会将s3转义成(*s3),s4同 var s3 *Stu = new(Stu) // 4. var s4 *Stu = &Stu{}
如果结构体的字段类型(引用类型)是指针、slice、map,他们的零值是nil,即没有分配空间,在字段使用之前需要先make()
结构体的所有字段在内存中是连续的
两个结构体无法进行转换(赋值=),取别名也不行,因为golang会认为这是两种数据类型,强制转换时字段要完全一样(包括名称、个数)
type A struct {num int} type B struct {n int} var a A var b B a = b // 报错 a = B(b) // 强制转换
- 结构体:自定义数据类型
-
结构体的大小是声明中各元素指针大小之和(string指针占16字节,int指针占8字节),可以使用unsafe.SizeOf() 查看。(go语言中string本质是结构体)
-
方法作用在指定数据类型上,所有自定义类型都可以有方法。
自定义数据类型会作为参数传递给方法,如果数据类型是值类型则进行值拷贝,如果是引用类型则进行地址拷贝。
结构体是值拷贝, 为了提高效率一般将方法和结构体的指针类型绑定
:
func (结构体形参 *结构体名) 方法名(方法参数1,…) {
fmt.Println("…")
}
饭粒:
func (a *A) test() {
fmt.Println("…")
}
test()方法与结构体A绑定,只能通过A的实例调用。
如果自定义类型绑定了自定义String方法,那么在打印地址时会按照自定义的String返回值输出。 -
方法和函数的区别:
- 调用方式不一样
- 对于函数,形参为值类型时不能将指针类型的数据作为实参传递,反之同理;
对于方法(如struct的方法),形参和实参不论值类型还是指针类型都可以相互调用。
注意:不管调用形式如何,真正决定是值拷贝还是地址拷贝得看这个方法是和哪个类型绑定。如果是和值类型绑定,比如(u User),则是值拷贝,如果是和指针类型绑定,如(u *User),则是地址拷贝。
package main import "fmt" type User struct { Name string } func (u User) change() { // 因为形参是值类型,所以实参不论是值传递还是引用传递都是值拷贝 u.Name = "yiming" fmt.Println("change() User.Name:", u.Name) } func main() { u1 := User{"fone"} (&u1).change() // 值拷贝 fmt.Println("main() User.Name:", u1.Name) }
-
面向对象特性
- 封装:逻辑写在方法中
- 继承:消灭代码冗余
通过嵌入匿名结构体实现:声明的时候加上一行需要继承的结构体名即可
说明:
- 继承过来的结构体所有属性和方法都可以使用(不论首字母大小写都可以)
- 匿名结构体的属性和方法调用可以简化匿名结构体名,查找顺序是(就近原则):先找当前结构体的属性方法,如果没有就找继承结构体的属性方法。
- 结构体嵌入两个(或多个)匿名结构体时,如果两个匿名结构体有同名字段或方法,而当前结构体没有同名字段方法,在调用时必须指定匿名结构体名,否则编译报错。(为了代码简洁性,尽量不要使用多重继承)
- 如果嵌入的是有名结构体(匿名结构体前带上别名),则调用属性方法时必须带上有名结构体别名。
- 嵌入匿名结构体使用指针类型会比较高效。
package main import "fmt" type Product struct { Name string Price float64 } type SuperMarket struct { Name string Address string } type cup struct { p *Product *SuperMarket } func (p *Product) Card() { fmt.Println("商品: ", p.Name, "\t价格:", p.Price) } func (sm *SuperMarket) MarketInfo() { fmt.Println("商场: ", sm.Name, "\t地址:", sm.Address) } func main() { c1 := cup{ &Product{ Name: "杯具001", Price: 12.34, }, &SuperMarket{ Name: "人人乐", Address: "学府路58号", }, } c1.p.Card() c1.MarketInfo() fmt.Println(c1.Name) }
-
接口interface
接口名一般使用er
结尾;
用于定义一组方法,但不需要实现。
接口不能包含任何变量。
定义接口函数时不能使用指针接收器。
在自定义类型(如结构体)的时候,再把这些方法通通实现。
接口定义中的所有方法都没有方法体(即都是没有实现的方法),接口体现了程序设计的多态
和高内聚低耦合
思想。
接口只要一个变量,含有接口类型中的所有方法,那么这个变量就实现了这个接口。
语法:type 接口名 interface{ 方法1(参数列表) [返回值列表] 方法2(参数列表) [返回值列表] } func (自定义类型别名 自定义类型) 方法1(参数列表) [返回值列表] {实现逻辑}...
注意:
- 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型变量(实例);
- 任何自定义类型都可以实现接口,一个自定义类型可以实现多个接口;
- 接口可以继承多个接口,此时自定义类型需要实现所有方法。但是不允许继承的接口存在相同的方法,即一个接口(包括继承过来的接口)中不能存在相同的方法。
- 接口类型默认是一个指针(引用类型),没有初始化时为nil;
- 空接口interface{}没有任何方法,所有类型都实现了空接口,任何变量都可以赋值给空接口。
- 接口可以在不破坏继承关系的基础上增加方法,实现接口是对继承的补充。
-
接口&继承
- 继承的价值:解决
代码复用性
和可维护性
; - 接口的价值:
设计
好各种规范(方法),让其他自定义类型去实现这些方法。 - 接口比继承更加灵活:继承是满足 is - a 的关系,而接口只需满足 like - a 的关系。
-
多态
变量(实例)具有多种形态。
多态通过接口实现。类型断言:当需要将一个接口变量赋值给自定义类型变量时使用类型断言。
饭粒:type Point struct { x int y int } func main() { var a interface{} var point Point = Point{1,2} a = point // 空接口可以接收任意类型 var b Point // 类型断言 // 判断a是否指向Point类型,如果是就转成Point类型并赋值给b,否则不抛异常。 if b, ok = a.(Point); ok { fmt.Println(a, b) } else { fmt.Println("panic..") } fmt.Println("exec continue..") }
类型断言应用场景:
- 在实现多态时,自定义变量需要调用自己单独的方法时,使用断言判断变量类型。
- 用于判断数据类型,饭粒:
package main import "fmt" type Student struct {} func GetType(items... interface{}) { for i, x := range items { switch x.(type) { case bool: fmt.Printf("第%v个值类型是 布尔, 值为:%v\n", i, x) case int, int32, int64: fmt.Printf("第%v个值类型是 整形, 值为:%v\n", i, x) case float32, float64: fmt.Printf("第%v个值类型是 浮点型, 值为:%v\n", i, x) case string: fmt.Printf("第%v个值类型是 字符串, 值为:%v\n", i, x) case Student: fmt.Printf("第%v个值类型是 学生, 值为:%v\n", i, x) case *Student: fmt.Printf("第%v个值类型是 学生指针, 值为:%v\n", i, x) default: fmt.Printf("第%v个值类型 不确定, 值为:%v\n", i, x) } } } func main() { n1 := 1 n2 := 1.1 n3 := "hello" n4 := true n5 := Student{} n6 := &Student{} GetType(n1, n2, n3, n4, n5, n6) }
-