概述
结构体是一种自定义类型,是不同数据的几何体,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 = 3k.Name ="li"k.Age = 30
类型嵌入
声明一个 struct 可以包含已经存在的 struct类型 或者go语言中内置类型作为内置字段,称为匿名字段,即只写了 typeName,无 varName,但是 typeName 不能重复。
package mainimport "fmt"type Person struct {name stringage 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 mainimport "fmt"type StudentFun struct {name stringage intscore 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 int64Name stringIsVIP bool}{Id:101,Name:"小明",IsVIP:true}response := struct {Data interface{}Msg stringCode 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 mainimport "fmt"type PersonNew struct {name string}type StudentNew struct {p *PersonNewscore 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 PersonNewscore 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 mainimport ("unsafe""fmt")type foo struct {myBool boolmyInt64 int64myInt int32}type bar struct{myInt64 int64myBool boolmyInt int32}func main() {f := foo{}fmt.Println(unsafe.Sizeof(f.myBool)) // 1fmt.Println(unsafe.Sizeof(f.myInt64)) // 8fmt.Println(unsafe.Sizeof(f.myInt)) // 4fmt.Println(unsafe.Sizeof(f)) // 24fmt.Println(unsafe.Offsetof(f.myBool)) // 0fmt.Println(unsafe.Offsetof(f.myInt64)) // 8fmt.Println(unsafe.Offsetof(f.myInt)) // 16fmt.Println("========内存对齐================")b := bar{}fmt.Println(unsafe.Sizeof(b.myBool)) // 1fmt.Println(unsafe.Sizeof(b.myInt64)) // 8fmt.Println(unsafe.Sizeof(b.myInt)) // 4fmt.Println(unsafe.Sizeof(b)) // 16fmt.Println(unsafe.Offsetof(b.myBool)) // 8fmt.Println(unsafe.Offsetof(b.myInt64)) // 0fmt.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
