内容简介:结构体(定义结构体的一般语法如下:
结构体( Struct )是复合类型,可以封装属性和操作(即字段和方法)。
Go 中的结构体类似面向对象编程中的轻量级类,但 Go 中没有类的概念,所以结构体尤为重要。
创建
定义结构体的一般语法如下:
type identifier struct {
field1 type1
field2 type2
...
}
复制代码
- 如果不需要
field,可以将其命名为_ - 结构体成员遵循
Go的导出原则,一个结构体可以同时包含导出和未导出的成员
初始化
定义结构体类型: Person
type Person struct {
Name string
Gender string
Age uint8
}
复制代码
结构体的 零值 是每个成员都是零值:
// 零值化
var p1 Person
fmt.Printf("%+v\n", p1) // {Name: Gender: Age:0}
// 选择器
p1.Name = "Ming"
p1.Age = 32
fmt.Printf("%+v\n", p1) // {Name:Ming Gender: Age:32}
复制代码
使用字段名:
- 使用字段名时,顺序可以打乱;
- 忽略字段名时,需要严格按照定义的顺序,且数量得对应;
- 未提供初始值的字段将使用该字段的类型的零值
p2 := Person{Name: "Qiang", Gender: "Male", Age: 30}
fmt.Printf("%+v\n", p2) // {Name:Qiang Gender:Male Age:30}
p3 := Person{Gender: "Male", Name: "Hua"}
fmt.Printf("%+v\n", p3) // {Name:Hua Gender:Male Age:0}
p4 := Person{"Jack", "Male", 30}
fmt.Printf("%+v\n", p4) // {Name:Jack Gender:Male Age:30}
复制代码
使用字段赋值时和 map 的键值对的写法有点类似,但请注意区别。
使用 new() :使用 new 函数给一个新的结构体变量分配内存,它返回指向* 已分配内存的指针
var p5 *Person
fmt.Println(p5 == nil) // true
p5 = new(Person)
fmt.Printf("%+v\n", p5) // &{Name: Gender: Age:0}
var p6 *Person = new(Person)
fmt.Printf("%+v\n", p6) // &{Name: Gender: Age:0}
p7 := new(Person)
fmt.Printf("%+v\n", p7) // &{Name: Gender: Age:0}
// 指针也有选择器,也可以使用类似属性的操作
p7.Name = "Ming"
fmt.Printf("%+v\n", p7) // &{Name:Ming Gender: Age:0}
复制代码
混合字面量语法( composite literal syntax )是一种简写,底层仍然会调用 new () :
p8 := &Person{"Dai", "Male", 42}
fmt.Printf("%+v\n", p8) // &{Name:Dai Gender:Male Age:42}
复制代码
注意, new(Type) 和 &Type{} 是等价的 ,见内存布局。
上述中的 p1 、 p2 等通常被称做类型 Person 的一个实例( instance )或对象( object )。
简写
当字段的类型相同时,可以写在同一行:
type T struct {
a, b int
}
t := T{1, 2}
fmt.Println(t.a) // 1
fmt.Println(t.b) // 2
复制代码
工厂方法
Go 中没有类的概念,不存在 OOP 中的构造方法,但是我们可以使用工厂方法实现类似的行为。
按照惯例,工厂函数的名字以 new 或 New 开头:
package main
import (
"fmt"
"unsafe"
)
type Person struct {
name string
age uint8
}
func NewPerson(name string, age uint8) *Person {
// 注意,这个返回的是局部变量的地址.
// 如果在 C++ 中是个典型的错误,Go 中没问题
return &Person{name, age}
}
func main() {
p := NewPerson("Jack", 20)
fmt.Printf("%+v\n", p) // &{name:Jack age:20}
fmt.Println(unsafe.Sizeof(Person{})) // 查看一个实例占用了多少内存
}
复制代码
这类似于面向对象中的实例化一个类。
选择器
无论一个 结构体类型 还是一个 结构体类型指针 ,都使用同样的选择器符来引用结构体的字段:
type Person struct {
name string
age uint8
}
p1 := Person{"Jack", 20} // 结构体类型变量
p2 := &Person{"Duncan", 42} // 指向一个结构体类型变量的指针
fmt.Println(p1.name, p1.age) // Jack 20
fmt.Println(p2.name, p2.age) // Duncan 42
复制代码
内存布局
用图说明结构体类型 Point 的实例和指向它的指针的内存布局:
type Point struct {
x, y int
}
复制代码
new(Point) 和 &Point{} 返回的是指针。
Go 语言中,结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体,这在性能上带来了很大的优势。不像 Java 中的引用类型,一个对象和它里面包含的对象可能会在不同的内存空间中。
举例:
package main
import (
"fmt"
"strings"
)
type Person struct {
name string
age uint8
}
// 这是方法,详细请见后续笔记
func update(p *Person) {
p.name = strings.ToUpper(p.name)
}
func main() {
p1 := Person{"Duncan", 43}
update(&p1)
fmt.Println(p1.name) // DUNCAN
p2 := new(Person)
p2.name = "Curry" // 指针也能使用选择器,Go自动做了转换,不像C++中那样需要使用 -> 操作符
update(p2)
fmt.Println(p2.name) // CURRY
(*p2).name = "Tony" // 也可以取指针的值
update(p2)
fmt.Println(p2.name) // TONY
p3 := &Person{"Durant", 30}
update(p3)
fmt.Println(p3.name) // DURANT
}
复制代码
匿名字段
匿名字段,即没有名字的字段,声明时只指定类型。
- 类型必须是命名的类型或指向一个命名的类型的指针
- 匿名字段的名字默认是其类型的名字,因此不能同时包含两个相同类型的匿名字段,这会导致名字冲突
- 匿名字段也有可见性的规则约束
type Person struct {
name string
age int
int // 匿名字段,不能有两个匿名字段 int
string
}
p1 := Person{}
fmt.Printf("%+v\n", p1) // {name: age:0 int:0 string:}
fmt.Println(p1.int) // 匿名字段的名字是其类型名
p2 := new(Person)
p2.int = 10
p2.string = "abc"
fmt.Printf("%+v\n", p2) // &{name: age:0 int:10 string:abc}
p := Person{"a", 10, 10}
fmt.Printf("%+v\n", p) // {Name:a Age:10 int:10}
复制代码
内嵌结构体
当一个结构体嵌套进另一个结构体在 Go 语言中也是很常见的,举例:
type Address struct {
province string
city string
}
type User struct {
name string
age int
address Address
}
u := &User{
name: "Ming",
age: 30,
address: Address{
province: "Jiangsu",
city: "Nanjing",
},
}
fmt.Printf("%+v\n", u) // &{name:Ming age:30 address:{province:Jiangsu city:Nanjing}}
复制代码
当结构体作为匿名成员的时候,会有一些特殊的用法:
type Address struct {
province string
city string
}
type User struct {
name string
age int
Address
}
// 方法一:正常直观方式定义
u1 := &User{
name: "Ming",
age: 30,
Address: Address{
province: "Jiangsu",
city: "Nanjing",
},
}
fmt.Printf("%+v\n", u1) // &{name:Ming age:30 Address:{province:Jiangsu city:Nanjing}}
// 同上
var u2 User
u2.name = "Qiang"
u2.age = 35
u2.Address = Address{province: "Jiangsu", city: "Suzhou"}
fmt.Printf("%+v\n", u2) // {name:Qiang age:35 Address:{province:Jiangsu city:Suzhou}}
// 方法二:匿名嵌入时可以直接访问叶子属性而不需要给出完整的路径
var u3 User
u3.name = "A"
u3.age = 40
u3.province = "Jiangsu"
u3.city = "Wuxi"
fmt.Printf("%+v\n", u3) // {name:A age:40 Address:{province:Jiangsu city:Wuxi}}
// 但下面的方式是错误的,编译不能通过
// cannot use promoted field Address.province in struct literal of type User
// cannot use promoted field Address.city in struct literal of type User
u4 := User{
name: "A",
age: 29,
province: "Jiangsu",
city: "Wuxi",
}
fmt.Printf("%+v\n", u4)
复制代码
内嵌结构体可以用来实现类似 OOP 中的继承
命名冲突
当两个字段拥有相同的名字(可能是继承来的名字)时:
- 外层名字会覆盖内层名字(但是两者的内存空间都保留),利用此特性可以实现字段或方法重载
- 如果相同的名字在同一级别出现了两次,当使用这个名字时将会引发一个错误(不使用没关系)。
type Group struct {
name string
id int
}
type Team struct {
id int
}
type User struct {
name string
Group
Team
}
var u User
// 当结构体中的字段与匿名成员内的字段冲突时,结构体的字段覆盖匿名成员的字段,但匿名成员的字段仍然存在
u.name = "A"
fmt.Printf("%+v\n", u) // {name:A Group:{name: id:0} Team:{id:0}}
u.Group.name = "B"
fmt.Printf("%+v\n", u) // {name:A Group:{name:B id:0} Team:{id:0}}
// 当匿名成员中的字段出现冲突,使用时会引起错误
fmt.Println(u.Group.id) // 正常
fmt.Println(u.Team.id) // 正常
fmt.Println(u.id) // ambiguous selector u.id
复制代码
其它
标签
tag 是结构体的元信息,是一个附属于字段的字符串,可以是文档或其他的重要标记。
标签的内容不能在一般的编程中使用,只有包 reflect 能获取它。
根据Go conventions, tag 有一些约定:
- tag 字符串是可选的空格分隔的 key-value
- key 是非空字符串,由空格(' ')、引号(' ')和冒号(':')以外的非控制字符组成
- value 使用双引号
"和Go字符串 - 检查字段是否为可导出
import (
"encoding/json"
"fmt"
"reflect"
)
type User struct {
Name string `json:"xxx"`
Sex string `json:"sex"`
Age uint8
}
func main() {
u := User{"Ming", "male", 30}
// 通过 reflect 包获取 tag
fmt.Println(reflect.TypeOf(u).Field(0).Tag) // json:"xxx"
fmt.Println(reflect.TypeOf(u).Field(1).Tag) // json:"sex"
fmt.Println(reflect.TypeOf(u).Field(2).Tag)
data, _ := json.Marshal(u)
fmt.Printf("%s\n", string(data))
// {"xxx":"Ming","sex":"male","Age":30}
// 注意字段名,Age 没添加 tag,默认为原来的
// User 结构体的非导出字段不会被 json 访问到
}
复制代码
递归结构体
结构体类型可以引用自身的指针类型,这就是递归结构体。
定义一个二叉树:
type tree struct {
value int
left *tree
right *tree
}
复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Designing Data-Intensive Applications
Martin Kleppmann / O'Reilly Media / 2017-4-2 / USD 44.99
Data is at the center of many challenges in system design today. Difficult issues need to be figured out, such as scalability, consistency, reliability, efficiency, and maintainability. In addition, w......一起来看看 《Designing Data-Intensive Applications》 这本书的介绍吧!
图片转BASE64编码
在线图片转Base64编码工具
HSV CMYK 转换工具
HSV CMYK互换工具