类型别名和自定义类型 自定义类型 Go语言中有一些的基本的数据类型,例如string
,整型
,浮点型
,布尔
等数据类型。
Go语言中可以使用type
关键字来定义自己的类型。
自定义类型是定义了一个全新的类型。可以基于内置的基本数据类型进行定义,也可以通过struct
定义。
1 2 3 4 5 6 7 type myInt int var a myInta = 100 fmt.Printf("myInt类型是%T, 值为%d\n" , a, a)
类型别名 TypeAlias只是Type的类型名字的别称,本质上TypeAlias 的类型与 Type类型是同一个类型。
rune
和byte
就是类型别名
1 2 type byte = uint8 type rune = int32
1 2 3 4 5 6 7 type TypeAlias = int var b TypeAliasb = 100 fmt.Printf("myInt类型是%T, 值为%d\n" , b, b)
结构体: 1 2 3 4 5 type 类型名 struct { 字段名 字段类型 字段名 字段类型 ... }
其中:
1 2 3 4 5 6 7 type person struct { name string age int gender string hobby []string }
或
1 2 3 4 5 type animal struct { name, city string age int }
1 2 3 4 5 6 7 8 type Dog struct { name string age int }
*struct是值类型* 1 2 3 4 5 6 p5 := person{"方式五" , "五" , 5 } fmt.Println(p5) p6 := p5 p6.street = "我是copy" fmt.Println(p5)
结构体实例化
基本实例化 1 2 3 4 5 6 7 8 9 10 var p personp.name = "八戒" p.age = 18 p.gender = "男" p.hobby = []string {"篮球" ,"足球" ,"双色球" } fmt.Printf("type: %T, value%v\n" , p, p) fmt.Println(p.name)
匿名结构体 1 2 3 4 5 6 7 8 var a struct { name string age int } a.name = "李四" a.age = 23 fmt.Printf("type: %T, value%v\n" , a, a)
创建指针类型结构体 Go语言支持对结构体指针直接使用.
来访问结构体的成员变量
1 2 3 4 5 var p = new (person)fmt.Printf("%T\n" , p) p.name = "张三" fmt.Printf("%#v" , p)
取结构体的地址实例化 使用&
对结构体进行取地址操作相当于对该结构题进行一次new
实例化操作
语法糖,自动根据内存地址找到那个原变量。
1 2 3 4 5 6 var p1 = new (person)p1.name = "方式一" p1.age = 1 p1.street = "一" fmt.Printf("%v\n" ,p1)
结构体初始化 没有初始化的结构体,其成员变量对应的其类型的零值
1 2 var p personfmt.Printf("%#v\n" ,p)
使用键值对初始化 1 2 3 4 5 6 7 p3 := person{ name : "方式三" , street: "三" , age : 3 , } fmt.Printf("%#v\n" ,p3)
也可以对其结构体指针初始化
1 2 3 4 5 6 p3_1 := &person{ name : "方式三_一" , street: "三——一" , age : 31 , } fmt.Printf("%#v\n" ,p3_1)
某些字段无初始值可以不写
1 2 3 4 5 p3_2 := &person{ name : "方式三_二" , age : 32 , } fmt.Printf("%#v\n" ,p3_2)
使用值列表初始化 1 2 3 4 5 6 7 p4 := person{ "方式四" , "四" , 4 , } fmt.Printf("%#v\n" ,p4)
注意:
1.必须初始化结构体所有字段。
2.初始值的时候填充顺序与字段在结构体中声明的顺序一致。
3.该方式不能与键值初始化方式混用。
结构体内存布局 结构体占用一块连续的内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type v struct { a int8 b int8 c int8 d int8 } func f () { n := v{1 ,2 ,3 ,4 } fmt.Println(&(n.a)) fmt.Println(&(n.b)) fmt.Println(&(n.c)) fmt.Println(&(n.d)) }
【进阶知识点】关于Go语言中的内存对齐推荐阅读:在 Go 中恰到好处的内存对齐
空结构体 空结构体不会占用内存空间。
1 2 3 4 var v struct {}fmt.Println(unsafe.Sizeof(v)) type null_struct struct {}
构造函数 Go语言没有内置的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type person struct { name string age int } func newPerson (name string , age int ) *person { return &person{ name: name, age: age, } } func main () { p := newPerson("张三" , 23 ) fmt.Println(p) }
注意:
方法和接收者 Go语言中的方法(Method)
是一种作用于特定类型类型变量的函数。这种特定类型变量叫做接收者(Receiver)
。接收者的概念就类似于其它语言中的this
或self
。
1 2 3 func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) { 函数体 }
其中:
接收者变量:接收者中的参数在命名时,官方建议使用接受者类型名称首字母小写,而不是self
,this
之类的命名。
接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
方法名、参数列表、返回参数:具体格式于函数定义相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func speak (p person) { fmt.Println(p.language) } func (p person) talk () { fmt.Println(p.language) } func main () { p := person{"张三" , 18 , "Chinese" } speak(p) p.talk() }
值类型接收者和指针类型接收者 指针类型的接受者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改一眼是有效的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func (p person) newYear () { p.age++ } func (p *person) realNewYear () { p.age++ } func main () { p := person{"张三" , 18 , "Chinese" } speak(p) p.talk() fmt.Println(p.age) p.newYear() fmt.Println(p.age) p.realNewYear() fmt.Println(p.age) }
什么时候应该使用指针类型接收者
1、需要修改接收者中的值
2、接收者是拷贝代价比较大的对象
3、保证一致性,如果某个方法使用了指针接收者,那么其他方法也应该使用指针接收者。
任意类型添加方法 在Go语言中,接收者可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。
包括最基础的数据类型,但是要使用自定义类型,再对其添加方法。
不能给别的包里面的类型添加方法,只能给自己的包里面的类型添加方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type myInt int func (m myInt) hello () { fmt.Println("我是一个int" ) } func main () { m := myInt(100 ) m.hello() }
结构体匿名字段 结构体的其成员字段在声明时没有名字,只有类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type person struct { string int } func main () { var p = person{ "张小三" , 18 , } fmt.Println(p.string ) fmt.Println(p.int ) }
匿名字段默认采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中的同类型的你们那个字段只能有一个。
嵌套结构体 一个结构体内可以包含另外一个结构体或者结构体指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 type person struct { name string age int addr address } type company struct { name string addr address } type address struct { province string city string } func main () { var c = company{ "字节跳动" , address{ "云南" , "昆明" , }, } fmt.Println(c) var p = person{ "张三" , 18 , address{ "重庆" , "渝北" , }, } fmt.Println(p)
匿名嵌套结构体 匿名嵌套结构体的好处可以直接.
内部嵌套的结构体的字段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 type person struct { name string age int address } type address struct { province string city string } func main () { p := person{ name: "张三" , age: 18 , address: address{ province: "成都" , city: "锦里" , }, } fmt.Println(p.city)
匿名嵌套结构体的字段冲突 嵌套结构体内可能存在相同的字段名,避免歧义就需指定具体嵌套的结构体的类型及字段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 type pig struct { name string address house } type address struct { province string city string } type house struct { city string price int } func main () { pig1 := pig{ name: "佩奇" , address: address{ "重庆" , "江北" , }, house: house{ "大理" , 8000 , }, } fmt.Println(pig1.address.city) fmt.Println(pig1.address.city) }
结构体的”继承“ Go语言可以使用结构体,,模拟其他编程语言中的面向对象的继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 type animal struct { name string } func (a animal) move () { fmt.Printf("%s会动!\n" , a.name) } type dog struct { feet uint8 animal } func (d dog) bark () { fmt.Printf("%s在叫:汪汪汪!!!\n" , d.name) } func main () { d1 := dog{ feet: 4 , animal: animal{ name:"佩奇" , }, } d1.bark() d1.move() }
结构体字段的可见性 结构体中小写表示私有,仅在此类中中可以使用,对其他类不可见。大写表示公开,在其他包可以直接使用,包含方法,属性等。
结构体与JSON 1 2 3 4 5 6 type person struct { Name string `json:"name" db:"name" ini:"name"` Age int `json:"age"` }
序列化 1 2 3 4 5 6 7 8 9 10 p := person{ Name: "张三" , Age: 18 , } var pstr,err = json.Marshal(p)if err != nil { fmt.Printf("序列化出错:%s" , err) } fmt.Println(string (pstr))
反序列化 1 2 3 4 5 6 p2str := `{"name":"李四","age":45}` var p2 personjson.Unmarshal([]byte (p2str), &p2) fmt.Println(p2)
结构体标签(Tag) Tag
是结构体的元信息,可以在运行的时候通过反射机制读取出来。Tag
在结构体的后方定义,由一对反引号包裹起来。
1 `key1:"value" key2:"value"`
同一个结构体字段可以设置多个键值对标签Tag
。
注意事项:
为结构体编写Tag
时,必须严格遵守键值对规则,结构体标签的解析代码的容错能力很弱,一旦格式写错,编译和运行是不会提示任何错误,通过反射也无法正确取值。例如不要在key与value之间加空格。
1 2 3 4 5 6 type person struct { Name string `json:"name" db:"name" ini:"name"` Age int `json:"age"` }
结构体和方法知识点补充 slice和map这两种数据类型都包含了指向底层数组的指针,我们在对其进行复制时要注意复制拷贝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 type Person struct { name string age int dreams []string } func (p *Person) SetDreams (dreams []string ) { p.dreams = dreams } func (p *Person) SetDreamsReal (dreams []string ) { p.dreams = make ([]string , len (dreams)) copy (p.dreams, dreams) } func main () { p1 := Person{name:"小王子" ,age:18 } data := []string {"吃饭" ,"睡觉" ,"打豆豆" } p1.SetDreams(data) fmt.Println(p1) data[1 ] = "不睡觉" fmt.Println(p1) p1.SetDreamsReal(data) fmt.Println(p1) data[1 ] = "学习" fmt.Println(p1) }