- golang结构体定义
- 结构体的访问属性
- 结构体的初始化、赋值
- 方式1:
- 方式2:
- 方式3:
- 方式4:
- 全部代码
- 匿名结构体
- 结构体的匿名字段
- 结构体的构造函数
- 构造函数
- 指针构造与对象构造
- 给结构体添加方法
- 结构体是值类型
- 结构体做结构体的类型成员
- 结构体内存布局与字节对齐问题
- 内存字节对齐
- 结构体大小
- 空结构体
- 结构体切片成员的传值问题
go语言的结构体定义需要使用关键字type, 格式如下
type 结构体名 struct{
成员1 类型
成员2 类型
。。。
。。。
}
例如下面定义一个Person结构体
//定义结构体
type Person struct {
name string
age int
gender string
hobby []string
}
结构体的访问属性
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。
结构体的初始化、赋值结构体的初始化、赋值大概有四种方式,
方式1://结构体定义方法 1
var p Person
p.name = "zhangsan"
p.age = 20
p.gender = "男"
p.hobby = []string{"篮球", "皮球", "双色球"}
//打印结构体内容
fmt.Println(p)
//访问字段
fmt.Println(p.name)
访问字段直接用结构体变量点出来即可
方式2://结构体定义方法 2
//new结构体
p2 := new(Person)
p2.age = 30
p2.name = "zhaoliu"
fmt.Printf("p2 type=%T \n", p2)
fmt.Printf("p2=%p \n", p2)
方式3:
//结构体定义方法 3
//声明时进行初始化
//注意需要使用逗号
p3 := Person{
name: "tianqi",
age: 17,
}
fmt.Println("p3=", p3)
方式4:
//结构体定义方法 4
//值列表初始化
//必须严格按照结构体里成员的顺序, 这种需要把所有字段都进行赋值,不然无法编译
p4 := Person{
"zhangwuji",
20,
"男",
[]string{"乾坤大挪移", "九阳神功", "太极拳"},
}
fmt.Println("p4=", p4)
全部代码
package main
import "fmt"
//定义结构体
type Person struct {
name string
age int
gender string
hobby []string
}
func main() {
//结构体定义方法 1
var p Person
p.name = "zhangsan"
p.age = 20
p.gender = "男"
p.hobby = []string{"篮球", "皮球", "双色球"}
//打印结构体内容
fmt.Println(p)
//访问字段
fmt.Println(p.name)
//结构体定义方法 2
//new结构体
p2 := new(Person)
p2.age = 30
p2.name = "zhaoliu"
fmt.Printf("p2 type=%T \n", p2)
fmt.Printf("p2=%p \n", p2)
//结构体定义方法 3
//声明时进行初始化
//注意需要使用逗号
p3 := Person{
name: "tianqi",
age: 17,
}
fmt.Println("p3=", p3)
//结构体定义方法 4
//值列表初始化
//必须严格按照结构体里成员的顺序, 这种需要把所有字段都进行赋值,不然无法编译
p4 := Person{
"zhangwuji",
20,
"男",
[]string{"乾坤大挪移", "九阳神功", "太极拳"},
}
fmt.Println("p4=", p4)
//匿名结构体
//不需要名字
var s struct {
x int
y int
}
s.x = 100
s.y = 100
// 打印结构体值用%v
fmt.Printf("s type = %T, s vale %v", s, s)
}
匿名结构体
可以在函数里直接定义结构体类型,不需要使用type关键字,这种称为匿名结构体.
//匿名结构体
//不需要名字
var s struct {
x int
y int
}
s.x = 100
s.y = 100
// 打印结构体值用%v
fmt.Printf("s type = %T, s vale %v", s, s)
结构体的匿名字段
结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。
//Person 结构体
type Person struct {
string
int
}
func main() {
p1 := Person{
"小王子",
18,
}
fmt.Printf("%#v\n", p1) //main.Person{string:"北京", int:18}
fmt.Println(p1.string, p1.int) //北京 18
}
注意:这里匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。
结构体的构造函数go语言没有像C++、java、c#这种面向对象的特性,在结构体里只能声明变量,不能声明方法,为了构造结构体更方便,可以在同一个包的外面给结构体声明方法,例如可以写构造函数:
package main
import "fmt"
type Student struct {
id int
name string
}
func newStudent(_id int, _name string) Student {
return Student{
_id, _name,
}
}
func newStudentPtr(_id int, _name string) *Student {
return &Student{
_id, _name,
}
}
//给结构体添加方法
func (this Student) sayHello() {
fmt.Printf("我的名字是%s, 学号%d\n", this.name, this.id)
}
//值接收
func (this Student) changeId() {
this.id++
}
//指针接收者
func (this *Student) changeIdYes() {
this.id++
}
func main() {
p1 := newStudent(1001, "张三")
fmt.Println(p1)
p1.sayHello()
p1.changeId()
fmt.Printf("id = %d\n", p1.id)
p1.changeIdYes()
fmt.Printf("id = %d\n", p1.id)
p2 := newStudentPtr(1002, "李四")
fmt.Println(p2)
}
例如上面的代码,声明了一个Student结构体,newStudent, newStudentPtr是两个构造函数,go语言没有方法重载的概念,所以不能有同名函数。
构造函数func newStudent(_id int, _name string) Student {
return Student{
_id, _name,
}
}
func newStudentPtr(_id int, _name string) *Student {
return &Student{
_id, _name,
}
}
指针构造与对象构造
当结构体成员比较多时,建议使用返回指针的形式进行构造,减少内存的开销,也就是newStudentPtr这种写法;当结构体成员比较少时,可以使用返回对象的写法,也就是newStudent这种写法。
给结构体添加方法Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。 格式如下
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
在上面的代码中,给结构体添加了3个方法
//给结构体添加方法
func (this Student) sayHello() {
fmt.Printf("我的名字是%s, 学号%d\n", this.name, this.id)
}
//值接收
func (this Student) changeId() {
this.id++
}
//指针接收者
func (this *Student) changeIdYes() {
this.id++
}
注意上面的this可以随便命名,只要是合理的标识符即可,用this,主要是参考C++的写法,更形象,也可以写成self。
结构体是值类型在方法changeId中,给id成员执行++操作,但是执行后并没有++, 如果想要用方法改变传入的值,得用指针传参,也就是changeIdYes()这种写法
结构体做结构体的类型成员package main
import "fmt"
type BaseInfo struct {
id int
name string
}
type Student struct {
gender string
baInfo BaseInfo
}
func main() {
var s1 Student
s1.baInfo.name = "张三"
s1.baInfo.id = 1001
s1.gender = "男"
fmt.Println(s1)
}
上面代码是把BaseInfo作为Student的成员 变量名baInfo 也可以不写,例如
package main
import "fmt"
type BaseInfo struct {
id int
name string
}
//type Student struct {
// gender string
// baInfo BaseInfo
//}
type Student struct {
gender string
BaseInfo
}
func main() {
//var s1 Student
//s1.baInfo.name = "张三"
//s1.baInfo.id = 1001
//s1.gender = "男"
var s1 Student
s1.name = "张三"
s1.id = 1001
s1.gender = "男"
fmt.Println(s1)
}
省略baInfo后,就可以用s1直接访问 这种嵌套定义有点类似C++、java的类继承,不过由于golang的特点,看起来不是那么明显
结构体内存布局与字节对齐问题go结构体有类似C/C++的内存对齐问题,例如下面的代码
package main
import "fmt"
type test struct {
a int8
b int8
c int8
d int8
}
func main() {
n := test{
1, 2, 3, 4,
}
fmt.Printf("n.a %p\n", &n.a)
fmt.Printf("n.b %p\n", &n.b)
fmt.Printf("n.c %p\n", &n.c)
fmt.Printf("n.d %p\n", &n.d)
}
输出 n.a 0xc000128058 n.b 0xc000128059 n.c 0xc00012805a n.d 0xc00012805b
内存字节对齐由于是int8,站一个字节,所以此时a,b,c,d的顺序是一致的,如果换成int32就不同了,如下所示
type test struct {
a int8
b int32
c int8
d int32
}
输出结果 n.a 0xc00001a0b0 n.b 0xc00001a0b4 n.c 0xc00001a0b8 n.d 0xc00001a0bc 由于要处理内存对齐问题,虽然a是8位,占一个字节,但是它的下一个成员b是占4个字节,为了字节对齐,那么a也是得占用4个字节,c, d类似,如下图:
fmt.Printf("结构体大小:%d", unsafe.Sizeof(n))
例如如上面结构体大小为16 n.a 0xc0000aa070 n.b 0xc0000aa074 n.c 0xc0000aa078 n.d 0xc0000aa07c 结构体大小:16
空结构体空结构体是不占用空间的。
var v struct{}
fmt.Println(unsafe.Sizeof(v)) // 0
结构体切片成员的传值问题
看下面的代码
package main
import "fmt"
type Person struct {
name string
age int8
dreams []string
}
func (p *Person) SetDreams(dreams []string) {
p.dreams = dreams
}
func main() {
p1 := Person{name: "小王子", age: 18}
data := []string{"吃饭", "睡觉", "打豆豆"}
p1.SetDreams(data)
fmt.Println(p1.dreams)
// 突然修改了data[1]的值
data[1] = "不睡觉"
fmt.Println(p1.dreams)
}
在设置Person的成员dreams后,不小心修改了slice的值,因为slice类型包含了指向底层数据的指针,此时Person的成员dreams也会被跟着修改 输出结果 [吃饭 睡觉 打豆豆] [吃饭 不睡觉 打豆豆] 为了避免这种情况,正确的做法是在方法中使用传入的slice的拷贝进行结构体赋值。
func (p *Person) SetDreams(dreams []string) {
p.dreams = make([]string, len(dreams))
copy(p.dreams, dreams)
}
这种问题存在于slice和map,在实际编码过程中一定要注意这个问题。