概述
结构体是一种自定义类型,是不同数据的几何体,struct是值类型,通常用来定义一个抽象的数据对象,
type struct_variable_type struct {
member definition;
member definition;
...
member definition;
}
type 结构体名 struct {
属性变量名 属性类型
属性变量名 属性类型
...
}
实例化
- 按照field:value的方式初始化,不需要按照struct中的变量名称顺序
m :=User{ID:2,Age:18,Name:"wang"}
2 .按照顺序提供初始化值,不需要显示说明struct中的变量名称,但是必须按照顺序
u :=User{1,"xiang",28}
- 通过New创建实例
k := new(User)
k.ID = 3
k.Name ="li"
k.Age = 30
类型嵌入
声明一个 struct 可以包含已经存在的 struct类型 或者go语言中内置类型作为内置字段,称为匿名字段,即只写了 typeName,无 varName,但是 typeName 不能重复。
package main
import "fmt"
type Person struct {
name string
age int
}
type Student struct {
Person // 匿名字段
score float32
}
func main() {
m := Person{"ming", 25}
fmt.Println(fmt.Sprintf("%v", m))
h := Person{"wang", 18}
fmt.Println(fmt.Sprintf("%v", h))
s := Student{Person{"li", 21}, 100}
fmt.Println(fmt.Sprintf("%v", s))
}
打印结果
{ming 25}
{wang 18}
{{li 21} 100}
成员方法
方法和函数的主要区别在方法是有实例接收参数的receiver,编译器以此来确定方法所属的类型。定义成员方法的格式如下:
func (r ReceiverType) funcName(parameters) (results)
func (变量名 类名称) 函数名(参数列表) (返回类型列表)
在方法内改变实例的属性的时候,必须使用指针做为方法的接收者。
package main
import "fmt"
type StudentFun struct {
name string
age int
score float32
}
func (s StudentFun) updateAge(age int){
s.age = age
}
func (s *StudentFun) updateScore(score float32){
s.score = score
}
func main() {
s := StudentFun{"li", 21, 100}
fmt.Println(fmt.Sprintf("before:%v", s))
s.updateAge(18)
s.updateScore(60)
fmt.Println(fmt.Sprintf("update:%v", s))
}
打印结果
before:{li 21 100}
update:{li 21 60}
注意上面的结果可以发现 只有指针接受者才可以改变属性值,因此只会改变分数,但是不会改变年龄值
在 Go 语言中,方法名的首字母大小被来实现控制对方法的访问权限。
- 当方法的首字母为大写时,这个方法对于所有包都是公开方法,其他包可以随意调用,类似Public
- 当方法的首字母为小写时,这个方法是私有方法,其他包是无法访问的。类似Private
同理结构体名称的大小写也和方法名一样,对于初学者来说需要特别注意。
匿名结构体
如果只是临时使用struct一次,而不是多次使用,用匿名struct即可,定义匿名结构体时没有 type
关键字,与其他定义类型的变量一样,如果在函数外部需在结构体变量前加上 var
关键字,在函数内部可省略 var
关键字。
user := struct {
Id int64
Name string
IsVIP bool
}{Id:101,Name:"小明",IsVIP:true}
response := struct {
Data interface{}
Msg string
Code int
}{Data:user,Msg:"success",Code:0}
make&new
new
// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type
内建函数 new 用来分配内存,第一个参数是一个类型,不是一个值,返回值是一个指向分配零值的指针
new和其他语言中的同名函数一样,new(t)分配了零值填充的类型为T内存空间,并且返回其地址,即一个t类型的值。返回的永远是类型的指针,指向分配类型的内存地址
它并不初始化内存,只是将其置零。t指向的内容的值为零(zero value)。注意并不是指针为零。
new(T) 为每个新的类型T分配一片内存,初始化为 0 并且返回类型为T的内存地址:这种方法 *返回一个指向类型为 T,值为 0 的地址的指针,它适用于值类型如数组和结构体;它相当于 &T{}。
特别需要注意的是下面情况可能出现空指针问题
package main
import "fmt"
type PersonNew struct {
name string
}
type StudentNew struct {
p *PersonNew
score float32
}
func main() {
s := new(StudentNew)
fmt.Println(s.p.name)
}
打印结果
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x4871a8]
goroutine 1 [running]:
修复上面的问题1,不要使用指针
type StudentNew struct {
p PersonNew
score float32
}
另外是需要判断
s := new(StudentNew)
if s.p!=nil {
fmt.Println(s.p.name)
}
make
// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
// Slice: The size specifies the length. The capacity of the slice is
// equal to its length. A second integer argument may be provided to
// specify a different capacity; it must be no smaller than the
// length. For example, make([]int, 0, 10) allocates an underlying array
// of size 10 and returns a slice of length 0 and capacity 10 that is
// backed by this underlying array.
// Map: An empty map is allocated with enough space to hold the
// specified number of elements. The size may be omitted, in which case
// a small starting size is allocated.
// Channel: The channel's buffer is initialized with the specified
// buffer capacity. If zero, or the size is omitted, the channel is
// unbuffered.
func make(t Type, size ...IntegerType) Type
内建函数 make 用来为 slice,map 或 chan 类型分配内存和初始化一个对象(注意:只能用在这三种类型上),跟 new 类似,第一个参数也是一个类型而不是一个值,跟 new 不同的是,make 返回类型的本身而不是指针,而返回值也依赖于具体传入的类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了
make(t, args)与new(t)的功能区别是,make只能创建slice、map和channel,并且返回一个初始化的类型为t的值(而不是*t)。
注意,因为这三种类型是引用类型,所以必须得初始化,但是 不是置为零值,这个和new是不一样的。
Struct内存对齐
package main
import (
"unsafe"
"fmt"
)
type foo struct {
myBool bool
myInt64 int64
myInt int32
}
type bar struct{
myInt64 int64
myBool bool
myInt int32
}
func main() {
f := foo{}
fmt.Println(unsafe.Sizeof(f.myBool)) // 1
fmt.Println(unsafe.Sizeof(f.myInt64)) // 8
fmt.Println(unsafe.Sizeof(f.myInt)) // 4
fmt.Println(unsafe.Sizeof(f)) // 24
fmt.Println(unsafe.Offsetof(f.myBool)) // 0
fmt.Println(unsafe.Offsetof(f.myInt64)) // 8
fmt.Println(unsafe.Offsetof(f.myInt)) // 16
fmt.Println("========内存对齐================")
b := bar{}
fmt.Println(unsafe.Sizeof(b.myBool)) // 1
fmt.Println(unsafe.Sizeof(b.myInt64)) // 8
fmt.Println(unsafe.Sizeof(b.myInt)) // 4
fmt.Println(unsafe.Sizeof(b)) // 16
fmt.Println(unsafe.Offsetof(b.myBool)) // 8
fmt.Println(unsafe.Offsetof(b.myInt64)) // 0
fmt.Println(unsafe.Offsetof(b.myInt)) // 12
}
结构体foo字段bool占用1字节,int64占用8字节,int32占用4字节。 但是在实例化结构体的时候,使用的内存并不是1 + 8 + 4 = 13字节, 实际上在分配内存的时候,有一个内存对齐规则。 第一个bool字段分配8字节的空间,第一个字节放入myBool,这时int64需要8个字节,剩下的7个字节不够放,所以Offset到下一个8字节的空间,依次类推,所以每个字段都是8字节,所以是 8 + 8 + 8 = 24字节。
参考
https://juejin.im/post/5ca2f37ce51d4502a27f0539
https://www.cnblogs.com/nickchen121/p/11517431.html#%E5%8C%BF%E5%90%8D%E7%BB%93%E6%9E%84%E4%BD%93
https://learnku.com/golang/t/29489#27d5ac