Go结构体&接口&并发&反射
一、结构体struct
0、Type关键字
Golang中通过type关键词定义一个结构体,需要注意的是,数组和结构体都是值类型
Go语言中可以使用type关键字来定义自定义类型:
type myInt int//通过type关键字的定义,mylnt就是一种新的类型,它具有int的特性
类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型
type TypeAlias = Type
rune 和 byte 就是类型别名,他们的底层代码如下:
type byte = uint8 type rune = int32
1、struct定义及使用
Go通过结构体struct和interface实现oop(面向对象编程)
struct的成员(也叫属性或字段)可以是任何类型,如普通类型、复合类型、函数、 map、 interface、
struct等
使用type 和 struct关键字来定义结构体:
type struct_variable_type struct { member member_type member member_type ..... member member_type } type Student struct { name string age int //小写私有成员(对外不可见) Class string //首字母大写则该成员为公有成员(对外可见) }
声明与初始化:
var stu1 Student stu1.name="zhangsan" //... var stu2 *Student= &Student{name:"zhangsan",age:10,Class:"1"} //返回指针,等同于new实例化操作 var stu3 = Student{name:"zhangsan",age:10,Class:"1"} var stu4 = new(Student) //...
自定义‘构造函数’方法:
func Newstu(name1 string,age1 int,class1 string) *Student { return &Student{name:name1,age:age1,Class:class1} } func main() { stu1 := Newstu("zhangsan"",34,"math") fmt.Println(stu1.name) }
注:go中的结构体内存布局和c结构体布局类似,每个成员的内存分布是连续的,结构体大小遵循对齐原则
2、struct tag
tag可以为结构体的成员添加说明或者标签便于使用,这些说明可以通过反射获取到
此外还可以方便进行json序列化操纵
type Student struct { ID string `json:"id1"` Gender string `json:"gender"` Name string `json:"name"` Sno string `json:"son"` }
获取tag信息:
var s1 = Student{} st := reflect.TypeOf(s1) field1 := st.Field(0) fmt.Println("key1:", field1.Tag.Get("json"))
json序列化操作:
// Json字符串转换成结构体 var str = `{"id1":"12","name":"李四","gender":"男","sno":"s001"}` var s2 = Student{} err := json.Unmarshal([]byte(str), &s2) if err != nil { fmt.Printf("转换失败 \n") } else { fmt.Printf("%#v \n", s2) }
注:要实现结构体转换成字符串,必须保证结构体中的字段是公有的,也就是首字母必须是大写的,这样才能够实现结构体 到 Json字符串的转换。
3、struct匿名成员
结构体中,每个成员不一定都有名称,也允许字段没有名字,即匿名成员
匿名成员的一个重要作用,可以用来实现oop中的继承
同一种类型匿名成员只允许最多存在一个
当匿名成员是结构体时,且两个结构体中都存在相同字段时,优先选择最近的字段
type Person struct { Name string Age int } type Student struct { score string Age int Person // 匿名内嵌结构体 } func main() { var stu = new(Student) stu.Age = 34 //优先选择Student中的Age fmt.Println(stu.Person.Age, stu.Age) // 0,34 }
4、struct继承
当结构体中的成员也是结构体时, 该结构体就继承了这个结构体,继承了其所有的方法与属性,当然有多个结构体成员也就是多继承
访问父结构中属性也使用“.”,但是当子结构体中存在和父结构中的字段相同时候,只能使用: "子结构体.父结构体.字段"访问父结构体中的属性,如上面示例的stu.Person.Age
继承结构体可以使用别名,访问的时候通过别名访问
type Person struct { Name string Age int } type Teacher struct { Salary int Classes string } type man struct { sex string job Teacher //别名,继承Teacher 这个时候就不是匿名了 Person //继承Person } func main() { var man1 = new(man) man1.sex = "man" man1.Age = 34 man1.Name = "zhangsan" man1.job.Classes = "111" man1.job.Salary = 100000 fmt.Println(man1, man1.job.Salary) }
5、struct方法
Go方法是作用在接受者上的一个函数,接受者是某种类型的变量,因此方法是一种特殊类型的函数。
接收者可以是任何类型,不仅仅是结构体, Go中的基本类型(int, string, bool等)也是可以,或者说数组的别名类型,甚至可以是函数类型。但是, 接受者不能是一个接口类型,因为接口是一个抽象的定义,方法是一个具体实现。
一个类型加上它的方法等价于面向对象中的一个类。一个重要的区别是:在 Go 中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在在不同的源文件, 唯一的要求是:它们必须是同一个包的。
类型 T(或 *T)上的所有方法的集合叫做类型 T(或 *T)的方法集。
因为方法是函数,所以同样的,不允许方法重载,即对于一个类型只能有一个给定名称的方法。但是如果基于接收者类型,是有重载的:具有同样名字的方法可以在 2 个或多个不同的接收者类型上存在,比如在同一个包里这么做是允许的
别名类型不能有它原始类型上已经定义过的方法(因为别名类型和原始类型底层是一样的)。
定义方法的格式
func(recv recevier_type(结构体))methodName(parameter_list)(return _value_list){}
结构体的指针方法
接收者是指针时,方法可以改变接受者的值(或状态),函数也能做到
当接受者是一个值的时候,这个值是该类型实例的拷贝;如果想要方法改变接受者的数据,就在接受者的指针类型上定义该方法
二、接口interface
1、接口定义和使用
interface(接口)是golang最重要的特性之一, Interface类型可以定义一组方法,但是这些不需要实
现,并且interface不能包含任何变量
interface 是方法的集合,interface是一种类型,并且是指针类型,interface的 更重要的作用在于多态实现
接口定义:
type 接口名称 interface { method1 (参数列表) 返回值列表 method2 (参数列表) 返回值列表 ... }
注:
- 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等,接口名最好突出该接口的类型含义。
- 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
- 参数列表、返回值列表:参数列表和返回值列表中的参数变量名是可以省略
接口的使用不仅仅针对结构体,自定义类型、变量等等都可以实现接口
要实现一个接口/使用接口对象, 接口接收的对象类型就必须实现该接口里面的所有方法
//定义接口 type Skills interface { Running() Getname() string } type Student struct { Name string Age int } // 实现接口 func (p Student) Getname() string { //实现Getname方法 fmt.Println(p.Name) return p.Name } func (p Student) Running() { // 实现 Running方法 fmt.Printf("%s running", p.Name) } // 使用接口 func main() { var skill Skills var stu1 Student stu1.Name = "zhangsan" stu1.Age = 34 skill = stu1 skill.Running() //调用接口 }
2、空接口
如果一个接口没有任何方法,我们称为空接口,由于空接口没有方法, 任意结构体都隐式地实现了空接口
// 空接口表示没有任何约束,任意的类型都可以实现空接口 type EmptyA interface { } func main() { var a EmptyA var str = "你好golang" // 让字符串实现A接口 a = str fmt.Println(a) }
golang中空接口也可以直接当做类型来使用,可以表示任意类型。相当于Java中的Object类型:
var a interface{} a = 20 a = "hello" a = true
空接口可以作为函数的参数,使用空接口可以接收任意类型的函数参数:
// 空接口作为函数参数 func show(a interface{}) { fmt.println(a) }
接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。这两部分分别称为接口的动态类型和动态值。
如果我们想要判断空接口中值的类型,那么这个时候就可以使用类型断言,其语法格式:
x.(T)//X:表示类型为interface{}的变量 T:表示断言x可能是的类型
// 类型断言 var a interface{} a = "132" value, isString := a.(string) if isString { fmt.Println("是String类型, 值为:", value) } else { fmt.Println("断言失败") }
使用switch语句来实现类型判断
func Print2(x interface{}) { switch x.(type) { case int: fmt.Println("int类型") case string: fmt.Println("string类型") case bool: fmt.Println("bool类型") default: fmt.Println("其它类型") } }
注:类型.(type) 只能结合switch语句使用
空接口如果值类型为切片,无法直接通过索引获取数组中的内容,只能使用类型断言:
// 这个时候我们就可以使用类型断言了 hobbyValue,ok := userInfo["hobby"].([]string) if ok { fmt.Println(hobbyValue[0]) }
3、接口多态及嵌套
go语言中interface是实现多态的一种形式,所谓多态, 就是一种事物的多种形态
同一个interface,不同的类型实现,都可以通过接口进行统一调用,也就是多态
func main() { var skill Skills var stu1 Student var t1 Teacher t1.Name = "zhangsan" stu1.Name = "lisi" stu1.Age = 22 skill = stu1 skill.Running() skill = t1 t1.Running() }
go语言中的接口可以嵌套,可以理解为继承,子接口拥有父接口的所有方法
如果使用该子接口,必须将父接口和子接口的所有方法都实现
接口的定义也支持组合嵌套(多嵌套)
type Skills interface { Running() Getname() string } type Test interface { sleeping() Skills //继承Skills }