【一Go到底】第三十五天---struct的内存布局及结构体

118 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第35天,点击查看活动详情

一、结构体和结构体变量区别及联系

  1. 结构体是自定义的数据类型,代表一类事物.
  2. 结构体变量(实例)是具体的,实际的,代表一个具体变量

二、struct的内存布局

2.1 案例演示

package main

import "fmt"

// 定义Cat结构体
type Cat struct {
	Name  string
	Age   int
	Color string
}

func main() {
	// 使用struct

	// 创建Cat变量
	var cat1 Cat
	//cat1 = { 0 }
	// cat1的地址= 0xc000100480
	fmt.Printf("cat1的地址= %p\n", &cat1)

	// 对变量赋值
	cat1.Name = "小白"
	cat1.Age = 3
	cat1.Color = "白色"
	// cat.Name 的地址 = 0xc000100480
	// cat.Age 的地址 = 0xc000100490
	// cat.Color 的地址 = 0xc000100498
	fmt.Printf("cat.Name 的地址 = %p\n", &cat1.Name)
	fmt.Printf("cat.Age 的地址 = %p\n", &cat1.Age)
	fmt.Printf("cat.Color 的地址 = %p\n", &cat1.Color)

}


三、结构体的声明和使用陷阱

3.1 语法(注意变量名大小写,是否在其他包可用)

type 结构体名称 struct{
    字段名 类型
    ......
}

// 声明案例
type Student struct{
    Name string
    Age int
    Score float64
}

3.2 结构体的字段/属性

3.2.1 基本介绍

  1. 从概念或叫法上看:结构体字段= 属性=field
  2. 字段是结构体的一个组成部分,一般是基本数据类型、数组,也可是引用类型。比如前面定义猫结构体的Name string就是属性

3.2.2 注意事项和说明

  1. 字段声明语法同变量,示例:字段名字段类型
  2. 字段的类型可以为:基本类型、数组或引用类型
  3. 在创建一个结构体变量后, 如果没有给字段赋值,都对应一个零值(默认值):
  • 布尔类型是false,数值是0,字符串是""。
  • 数组类型的默认值和它的元素类型相关,比如score [3]int则为[0, 0, 0]
  • 指针,slice, 和map的零值都是nil, 即还没有分配空间,需要先make。详见案例3.2.3案例一
  1. 不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,不影响另外一个。详见案例3.2.4案例二

3.2.3 案例一


package main

import "fmt"

// 若结构体字段类型是 : 指针,slice,map 的零值都是nil,未分配空间,需要先进行make

type Person struct {
	Name   string
	Age    int
	Scores [3]float64
	ptr    *int              // 指针类型  ,需要 new
	slice  []int             // 切片
	strMap map[string]string // map
}

func main() {

	// 定义结构体变量

	var p1 Person

	// 查看p1的内容
	// p1 = { 0 [0 0 0] <nil> [] map[]}
	fmt.Printf("p1 = %v\n", p1)

	// 测试指针 切片 map三种类型的初值
	// ptr nil
	// slice nill
	// strMap nil
	if p1.ptr == nil {
		fmt.Println("ptr nil")
	}
	if p1.slice == nil {
		fmt.Println("slice nill")
	}
	if p1.strMap == nil {
		fmt.Println("strMap nil")
	}

	// 使用slice
	p1.slice = make([]int, 10)
	p1.slice[0] = 1000
	// p1.slice = [1000 0 0 0 0 0 0 0 0 0]
	fmt.Printf("p1.slice = %v\n", p1.slice)

	// 使用map
	p1.strMap = make(map[string]string)
	p1.strMap["Name"] = "feng"
	// p1.strMap["Name"] = feng
	fmt.Printf("p1.strMap[\"Name\"] = %v\n", p1.strMap["Name"])
}

3.2.4 案例二

package main

import "fmt"

// 若结构体字段类型是 : 指针,slice,map 的零值都是nil,未分配空间,需要先进行make

type Person struct {
	Name   string
	Age    int
	Scores [3]float64
	ptr    *int              // 指针类型  ,需要 new
	slice  []int             // 切片
	strMap map[string]string // map
}

type Student struct {
	Name string
	Age  int
}

func main() {

	// 定义结构体变量

	var p1 Person

	// 查看p1的内容
	// p1 = { 0 [0 0 0] <nil> [] map[]}
	fmt.Printf("p1 = %v\n", p1)

	// 测试指针 切片 map三种类型的初值
	// ptr nil
	// slice nill
	// strMap nil
	if p1.ptr == nil {
		fmt.Println("ptr nil")
	}
	if p1.slice == nil {
		fmt.Println("slice nill")
	}
	if p1.strMap == nil {
		fmt.Println("strMap nil")
	}

	// 使用slice
	p1.slice = make([]int, 10)
	p1.slice[0] = 1000
	// p1.slice = [1000 0 0 0 0 0 0 0 0 0]
	fmt.Printf("p1.slice = %v\n", p1.slice)

	// 使用map
	p1.strMap = make(map[string]string)
	p1.strMap["Name"] = "feng"
	// p1.strMap["Name"] = feng
	fmt.Printf("p1.strMap[\"Name\"] = %v\n", p1.strMap["Name"])

	/***********************************************************/

	// 不同结构体的变量字段独立
	var stu1 Student

	stu1.Name = "Aliy"
	stu1.Age = 22
	// stu1 名字 =   Aliy , 年龄 =  22
	fmt.Println("stu1 名字 =  ", stu1.Name, ", 年龄 = ", stu1.Age)

	stu2 := stu1
	stu2.Name = "Bob"
	// stu2 名字 =   Bob , 年龄 =  22
	fmt.Println("stu2 名字 =  ", stu2.Name, ", 年龄 = ", stu2.Age)

	// 若想影响到原来的字段的变量值
	stu3 := &stu1
	stu3.Name = "Cindy"
	// stu3 名字 =   Cindy , 年龄 =  22
	fmt.Println("stu3 名字 =  ", stu3.Name, ", 年龄 = ", stu3.Age)
	// stu1 名字 =   Cindy , 年龄 =  22
	fmt.Println("stu1 名字 =  ", stu1.Name, ", 年龄 = ", stu1.Age)

}

四、创建结构体变量和访问结构体字段

4.1 方式一

var 变量名 struct类型

4.2 方式二

var 变量名 struct类型=struct类型{}

案例

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	// 声明实例
	var person Person = Person{"Aliy", 22}
	// 或者
	person2 := Person{"Bob", 20}

	// 或者
	person3 := Person{}
	person3.Name = "Cindy"
	person3.Age = 18

	// person = {Aliy 22}
	fmt.Printf("person = %v\n", person)
	// person2 = {Bob 20}
	fmt.Printf("person2 = %v\n", person2)
	// person3 = {Cindy 18}
	fmt.Printf("person3 = %v\n", person3)
}

4.3 方式三

var 变量名 *struct类型 = new(struct类型)

案例

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	// 声明方式三

	var person *Person = new(Person)

	// 因为 person是一个指针,因此便准的给字段赋值方式如下
	(*person).Name = "AliyPtr"
	(*person).Age = 22
	// person = {AliyPtr 22}
	fmt.Printf("person = %v\n", *person)

	// go设计者为使程序员使用方便,在底层对person.Name 进行处理,自动给person加上取值运算
	// 形成这样 (*person).Name
	// 可以直接进行如下操作
	person.Name = "Aliy"
	// no * ----> {Aliy 22}
	fmt.Printf("no * ----> %v", *person)
}

4.4 方式四

var 变量名 *struct类型 = &struct类型{}

案例

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	// 声明方式四

	var person *Person = &Person{}

	// 因为person是一个指针,标准访问如下
	// (*person).Name = "AliyPrt"
	// 类似方式三,也可以像如下写法
	person.Name = "Aliy"
	person.Age = 22
	// person = {Aliy 22}
	fmt.Printf("person = %v\n", *person)
}

4.5 总结

  1. 第3种和第4种方式返回的是结构体指针。
  2. 结构体指针访问字段的标准方式应该是: (*结 构体指针).字段名,比如(*person).Name = "tom"
  3. 但go做了-一个简化,也支持结构体指针.字段名,比如person.Name = "tom"。更加符合程序员使用的习惯,go编译器底层对person.Name做了转化(*person).Name