6.7 KiB
10.1 结构体定义
结构体定义的一般方式如下:
type identifier struct {
field1 type1
field2 type2
...
}
type T struct {a, b int}
也是合法的语法,它更适用于简单的结构体。
这个结构体里的成员都有名字(names),像field1,field2等,如果成员在代码从来也不会被用到,那么可以命名为*_*。
成员可以是任何类型,甚至是结构体本身(参考10.5),可以是函数或者接口(参考第11章)。可以定义结构体类型的一个变量,然后给它的成员像下面这样赋值:
var s T
s.a = 5
s.b = 8
数组可以看作是一种结构体类型,不过它使用下标而不是命名的成员。
使用new
使用new函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针:var t *T = new(T)
,如果需要可以把这条语句放在不同的行(比如定义是包范围的,但是分配却没有必要在开始就做)。
var t *T
t = new(T)
写这条语句的惯用法是:t := new(T)
,变量t是一个指向T
的指针,此时结构体成员的值是它们所属类型的零值。
声明var t T
也会给t
分配内存,并零值化内存,但是这个时候t是类型T。在这两种方式中,t通常被称做类型T的一个实例(instance)或对象(Object)。
Listing 10.1—structs_fields.go给出了一个非常简单的例子:
package main
import "fmt"
type struct1 struct {
i1 int
f1 float32
str string
}
func main {
ms := new(struct1)
ms.i1 = 10
ms.f1 = 15.5
ms.str= "Chris"
fmt.Printf("The int is: %d\n", ms.i1)
fmt.Printf("The float is: %f\n", ms.f1)
fmt.Printf("The string is: %s\n", ms.str)
fmt.Println(ms)
}
输出:
The int is: 10
The float is: 15.500000
The string is: Chris
&{10 15.5 Chris}
使用fmt.Println打印一个结构体的默认输出可以很好的显示它的内容,类似使用*%v*选项。
可以通过逗号符给成员赋不同的值,就像在面向对象语言中那样:structname.fieldname = value
。
使用同样的逗号符可以获取结构体成员的值:structname.fieldname
。
在Go中这叫选择器(selector)。无论变量是一个结构体类型还是一个结构体类型指针,都使用同样的选择器符(selector-notation)来引用结构体的成员:
type myStruct struct { i int }
var v myStruct // v has struct type
var p *myStruct // p is a pointer to a struct
v.i
p.i
初始化一个结构体实例(一个结构体字面量:struct-literal)的更简短和惯用的方式如下:
ms := &struct1{10, 15.5, "Chris"}
// 此时ms的类型是 *struct1
或者:
var mt struct1
ms := struct1{10, 15.5, "Chris"}
混合字面量语法(composite literal syntax) &struct1{a, b, c}
是一种简写,底层仍然会调用 new ()
,这里值的顺序必须按照成员顺序来写。在下面的例子中能看到可以通过在值的前面放上成员名来初始化值。表达式 new(Type)
和 &Type{}
是等价的。
结构体的典型例子是一个时间间隔(开始和结束时间以秒为单位):
type Interval struct {
start int
end int
}
初始化方式:
intr := Interval(0, 3) (A)
intr := Interval(end:5, start:1) (B)
intr := Interval(end:5) (C)
在(A)中值必须以成员在结构体定义时的顺序给出,*&*不是必须的。(B)显示了另一种方式,成员名加上一个冒号在值的前面,这种情况下值的顺序不必一致,并且某些成员还可以被忽略掉,就像(C)那样。
结构体类型和成员的命名遵循可见性规则(4.2),一个导出的结构体类型中有些成员是导出的,另一些不是,这是可能的。
下图说明了结构体类型实例和一个指向它的指针的内存布局:
type Point struct { x, y int }
使用new初始化:
作为结构体字面量初始化:
类型strcut1在定义它的包pack1中必须是唯一的,它的完全类型名是:pack1.struct1
。
下面的例子Listing 10.2—person.go显示了一个结构体Person,一个方法,方法有一个类型为*Person的参数(因此对象本身是可以被改变的),以及三种不同的调用这个方法的方式:
package main
import (
"fmt"
"strings"
)
type Person struct {
firstName string
lastName string
}
func upPerson(p *Person) {
p.firstName = strings.ToUpper(p.firstName)
p.lastName = strings.ToUpper(p.lastName)
}
func main() {
// 1-struct as a value type:
var pers1 Person
pers1.firstName = "Chris"
pers1.lastName = "Woodward"
upPerson(&pers1)
fmt.Printf("The name of the person is %s %s\n", pers1.firstName, pers1.lastName)
// 2—struct as a pointer:
pers2 := new(Person)
pers2.firstName = "Chris"
pers2.lastName = "Woodward"
(*pers2).lastName = "Woodward" // 这是合法的
upPerson(pers2)
fmt.Printf("The name of the person is %s %s\n", pers2.firstName, pers2.lastName)
// 3—struct as a literal:
pers3 := &Person{"Chris","Woodward"}
upPerson(pers3)
fmt.Printf("The name of the person is %s %s\n", pers3.firstName, pers3.lastName)
}
第2种情况
输出:
The name of the person is CHRIS WOODWARD
The name of the person is CHRIS WOODWARD
The name of the person is CHRIS WOODWARD
在上面例子的第二种情况中,像pers2.lastName="Woodward"
这样,可以直接通过指针,像pers2.lastName="Woodward"
这样给结构体成员赋值,没有像C++那样需要使用->
操作符,Go会自动做这样的转换。
注意也可以通过解指针的方式来设置值:
(*pers2).lastName = "Woodward"
结构体和内存布局
Go语言中的结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体,这带来了很大的性能优势。不像Java中的引用类型,一个对象和它里面包含的对象可能会在不同的内存空间中,Go中的指针和这个很像。下面的例子清晰的说明了这些情况:
type Rect1 struct {Min, Max Point }
type Rect2 struct {Min, Max *Point }
递归结构体
练习9.2
通过使用 unsafe 包中的方法来测试你电脑上一个整型变量占用多少个字节。
链接
- 目录
- 上一节:10 结构(struct)与方法(method)
- 下一节:regexp 包