概述

结构体是一种自定义类型,是不同数据的几何体,struct是值类型,通常用来定义一个抽象的数据对象,

  1. type struct_variable_type struct {
  2. member definition;
  3. member definition;
  4. ...
  5. member definition;
  6. }
  7. type 结构体名 struct {
  8. 属性变量名 属性类型
  9. 属性变量名 属性类型
  10. ...
  11. }

结构体里的字段可以是任意类型,包括结构体、函数、接口

实例化

  1. 按照field:value的方式初始化,不需要按照struct中的变量名称顺序
  1. m :=User{ID:2,Age:18,Name:"wang"}

2 .按照顺序提供初始化值,不需要显示说明struct中的变量名称,但是必须按照顺序

  1. u :=User{1,"xiang",28}
  1. 通过New创建实例
  1. k := new(User)
  2. k.ID = 3
  3. k.Name ="li"
  4. k.Age = 30

类型嵌入

声明一个 struct 可以包含已经存在的 struct类型 或者go语言中内置类型作为内置字段,称为匿名字段,即只写了 typeName,无 varName,但是 typeName 不能重复。

  1. package main
  2. import "fmt"
  3. type Person struct {
  4. name string
  5. age int
  6. }
  7. type Student struct {
  8. Person // 匿名字段
  9. score float32
  10. }
  11. func main() {
  12. m := Person{"ming", 25}
  13. fmt.Println(fmt.Sprintf("%v", m))
  14. h := Person{"wang", 18}
  15. fmt.Println(fmt.Sprintf("%v", h))
  16. s := Student{Person{"li", 21}, 100}
  17. fmt.Println(fmt.Sprintf("%v", s))
  18. }

打印结果

  1. {ming 25}
  2. {wang 18}
  3. {{li 21} 100}

成员方法

方法和函数的主要区别在方法是有实例接收参数的receiver,编译器以此来确定方法所属的类型。定义成员方法的格式如下:

  1. func (r ReceiverType) funcName(parameters) (results)
  2. func (变量名 类名称) 函数名(参数列表) (返回类型列表)

在方法内改变实例的属性的时候,必须使用指针做为方法的接收者。

  1. package main
  2. import "fmt"
  3. type StudentFun struct {
  4. name string
  5. age int
  6. score float32
  7. }
  8. func (s StudentFun) updateAge(age int){
  9. s.age = age
  10. }
  11. func (s *StudentFun) updateScore(score float32){
  12. s.score = score
  13. }
  14. func main() {
  15. s := StudentFun{"li", 21, 100}
  16. fmt.Println(fmt.Sprintf("before:%v", s))
  17. s.updateAge(18)
  18. s.updateScore(60)
  19. fmt.Println(fmt.Sprintf("update:%v", s))
  20. }

打印结果

  1. before:{li 21 100}
  2. update:{li 21 60}

注意上面的结果可以发现 只有指针接受者才可以改变属性值,因此只会改变分数,但是不会改变年龄值
在 Go 语言中,方法名的首字母大小被来实现控制对方法的访问权限。

  • 当方法的首字母为大写时,这个方法对于所有包都是公开方法,其他包可以随意调用,类似Public
  • 当方法的首字母为小写时,这个方法是私有方法,其他包是无法访问的。类似Private

同理结构体名称的大小写也和方法名一样,对于初学者来说需要特别注意。

匿名结构体

如果只是临时使用struct一次,而不是多次使用,用匿名struct即可,定义匿名结构体时没有 type 关键字,与其他定义类型的变量一样,如果在函数外部需在结构体变量前加上 var 关键字,在函数内部可省略 var 关键字。

  1. user := struct {
  2. Id int64
  3. Name string
  4. IsVIP bool
  5. }{Id:101,Name:"小明",IsVIP:true}
  6. response := struct {
  7. Data interface{}
  8. Msg string
  9. Code int
  10. }{Data:user,Msg:"success",Code:0}

make&new

new

  1. // The new built-in function allocates memory. The first argument is a type,
  2. // not a value, and the value returned is a pointer to a newly
  3. // allocated zero value of that type.
  4. func new(Type) *Type

内建函数 new 用来分配内存,第一个参数是一个类型,不是一个值,返回值是一个指向分配零值的指针
new和其他语言中的同名函数一样,new(t)分配了零值填充的类型为T内存空间,并且返回其地址,即一个t类型的值。返回的永远是类型的指针,指向分配类型的内存地址
它并不初始化内存,只是将其置零。
t指向的内容的值为零(zero value)。注意并不是指针为零。
new(T) 为每个新的类型T分配一片内存,初始化为 0 并且返回类型为T的内存地址:这种方法 *返回一个指向类型为 T,值为 0 的地址的指针,它适用于值类型如数组和结构体;它相当于 &T{}。

特别需要注意的是下面情况可能出现空指针问题

  1. package main
  2. import "fmt"
  3. type PersonNew struct {
  4. name string
  5. }
  6. type StudentNew struct {
  7. p *PersonNew
  8. score float32
  9. }
  10. func main() {
  11. s := new(StudentNew)
  12. fmt.Println(s.p.name)
  13. }

打印结果

  1. panic: runtime error: invalid memory address or nil pointer dereference
  2. [signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x4871a8]
  3. goroutine 1 [running]:

修复上面的问题1,不要使用指针

  1. type StudentNew struct {
  2. p PersonNew
  3. score float32
  4. }

另外是需要判断

  1. s := new(StudentNew)
  2. if s.p!=nil {
  3. fmt.Println(s.p.name)
  4. }

make

  1. // The make built-in function allocates and initializes an object of type
  2. // slice, map, or chan (only). Like new, the first argument is a type, not a
  3. // value. Unlike new, make's return type is the same as the type of its
  4. // argument, not a pointer to it. The specification of the result depends on
  5. // the type:
  6. // Slice: The size specifies the length. The capacity of the slice is
  7. // equal to its length. A second integer argument may be provided to
  8. // specify a different capacity; it must be no smaller than the
  9. // length. For example, make([]int, 0, 10) allocates an underlying array
  10. // of size 10 and returns a slice of length 0 and capacity 10 that is
  11. // backed by this underlying array.
  12. // Map: An empty map is allocated with enough space to hold the
  13. // specified number of elements. The size may be omitted, in which case
  14. // a small starting size is allocated.
  15. // Channel: The channel's buffer is initialized with the specified
  16. // buffer capacity. If zero, or the size is omitted, the channel is
  17. // unbuffered.
  18. 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内存对齐

  1. package main
  2. import (
  3. "unsafe"
  4. "fmt"
  5. )
  6. type foo struct {
  7. myBool bool
  8. myInt64 int64
  9. myInt int32
  10. }
  11. type bar struct{
  12. myInt64 int64
  13. myBool bool
  14. myInt int32
  15. }
  16. func main() {
  17. f := foo{}
  18. fmt.Println(unsafe.Sizeof(f.myBool)) // 1
  19. fmt.Println(unsafe.Sizeof(f.myInt64)) // 8
  20. fmt.Println(unsafe.Sizeof(f.myInt)) // 4
  21. fmt.Println(unsafe.Sizeof(f)) // 24
  22. fmt.Println(unsafe.Offsetof(f.myBool)) // 0
  23. fmt.Println(unsafe.Offsetof(f.myInt64)) // 8
  24. fmt.Println(unsafe.Offsetof(f.myInt)) // 16
  25. fmt.Println("========内存对齐================")
  26. b := bar{}
  27. fmt.Println(unsafe.Sizeof(b.myBool)) // 1
  28. fmt.Println(unsafe.Sizeof(b.myInt64)) // 8
  29. fmt.Println(unsafe.Sizeof(b.myInt)) // 4
  30. fmt.Println(unsafe.Sizeof(b)) // 16
  31. fmt.Println(unsafe.Offsetof(b.myBool)) // 8
  32. fmt.Println(unsafe.Offsetof(b.myInt64)) // 0
  33. fmt.Println(unsafe.Offsetof(b.myInt)) // 12
  34. }

结构体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