【11.0】Go语言基础之结构体

发布时间 2023-11-12 16:39:03作者: Chimengmeng

【一】什么是结构体

  • 结构体是用户定义的类型,表示若干个字段(Field)的集合。
    • 有时应该把数据整合在一起,而不是让这些数据没有联系。
    • 这种情况下可以使用结构体。
  • 例如,一个职员有firstName 、 lastName和age 三个属性,而把这些属性组合在一个结构体employee中就很合理。

【二】结构体的定义和使用

【1】结构体语法

type 名字 struct{
	字段1 类型
	字段2 类型
}

// 定义了一个结构体,用户自定义的类型
type Person struct {
	name string
	age  uint8
	sex string
	hobby []string
}

【2】结构体的定义与使用

package main

import "fmt"

// 结构体 :
//结构体是用户定义的类型,表示若干个字段(Field)的集合。
//有时应该把数据整合在一起,而不是让这些数据没有联系。这种情况下可以使用结构体

// 结构体的标准语法
/*
type 名字 struct{
	字段1 类型
	字段2 类型
}
*/

// [1]定义了一个结构体,用户自定义的类型
type Person struct {
	name  string
	age   uint8
	sex   string
	hobby []string
}

func main() {
	// [2]使用结构体
	var person Person

	// 结构体是值类型 : 定义没有初始化,打印后有值
	fmt.Println("这是定义的结构体 :>>>> ", person) // 这是定义的结构体 :>>>>  { 0  []}
}

【3】结构体初始化

package main

import "fmt"

// [1]定义了一个结构体,用户自定义的类型
type Person struct {
	name  string
	age   uint8
	sex   string
	hobby []string // 切片类型
}

func main() {
	// [2]使用结构体并且始化
	var person Person = Person{}
	fmt.Println("这是定义的结构体无值传入 :>>>> ", person) // 这是定义的结构体无值传入 :>>>>  { 0  []}

	hobby := make([]string, 3, 4)
	var person1 Person = Person{"Dream", 18, "男", hobby} // 按位置传参,符合顺序,并且不能少传参数
	fmt.Println("这是定义的结构体有值传入 :>>>> ", person1)          // 这是定义的结构体有值传入 :>>>>  {Dream 18 男 [  ]}

	var person2 Person = Person{name: "Dream", age: 18, sex: "男"} // 不按位置传参,可以不符合顺序,并且可以少传参数
	fmt.Println("这是定义的结构体有值传入 :>>>> ", person2)                   // 这是定义的结构体有值传入 :>>>>  {Dream 18 男 []}

}

【4】结构体使用

package main

import "fmt"

// [1]定义了一个结构体,用户自定义的类型
type Person struct {
	name  string
	age   uint8
	sex   string
	hobby []string // 切片类型
}

func main() {
	// [2]使用结构体并且始化

	hobby := make([]string, 3, 4)
	var person Person = Person{"Dream", 18, "男", hobby} // 按位置传参,符合顺序,并且不能少传参数
	fmt.Println("这是定义的结构体有值传入 :>>>> ", person)          // 这是定义的结构体有值传入 :>>>>  {Dream 18 男 [  ]}

	// 结构体的使用
	// 取值
	fmt.Println("这是取值并打印 :>>>> ", person.name) // 这是取值并打印 :>>>>  Dream

	// 修改值
	person.name = "Hope"
	fmt.Println("这是修改值并打印 :>>>> ", person.name) // 这是修改值并打印 :>>>>  Hope

	// 增加值 ---> 切片类型,必须先初始化才能使用
	person.hobby[0] = "音乐"
	// 会报错,越界。切片只能按长度的下标取值,不能按容量的下标取值
	// person.hobby[3] = "乒乓球" // panic: runtime error: index out of range [3] with length 3
	// append 追加值
	person.hobby = append(person.hobby, "乒乓球")
	fmt.Println("这是增加值并打印 :>>>> ", person.hobby) // 这是增加值并打印 :>>>>  [音乐   乒乓球]

}

【5】创建匿名结构体

package main

func main() {
	// 创建匿名结构体 ---> 结构体没有名字 ----> 一般定义在函数内部 ----> 只使用一次
	// 没有 type 和 名字
	p := struct {
		name string
		age  int
	}{
		// 定义匿名结构体并初始化
		name: "Dream",
		age:  18,
	}
}

【6】结构体的零值

package main

import "fmt"

type Person struct {
	name  string
	age   uint8
	sex   string
	hobby []string // 切片类型
}

func main() {
	// 结构体 的零值 --- 结构体是值类型 --- 空值不为 nil , 是每个结构体参数类型的零值
	var person Person
	fmt.Println("结构体 person 的零值 :>>>> ", person) // 结构体 person 的零值 :>>>>  { 0  []}
}

【7】结构体当函数参数传递

package main

import "fmt"

type Person struct {
	name  string
	age   uint8
	sex   string
	hobby []string // 切片类型
}

func main() {
	// 结构体作为函数参数
	var person Person
	fmt.Println("结构体 person 的初识值 :>>>> ", person)

	// 调用函数 --- 值类型当参数传递不会影响原来的值
	index(person)
	fmt.Println("调用函数后 ,结构体 person 的初识值 :>>>> ", person)

	//结构体 person 的初识值 :>>>>  { 0  []}
	//这是在函数内部修改过后的结构体 :>>>>  {梦梦 0  []}
	//调用函数后 ,结构体 person 的初识值 :>>>>  { 0  []}
}

func index(p Person) {
	p.name = "梦梦"
	fmt.Println("这是在函数内部修改过后的结构体 :>>>> ", p)
}

【8】访问结构体字段

package main

import "fmt"

type Person struct {
	name  string
	age   uint8
	sex   string
	hobby []string // 切片类型
}

func main() {
	// 访问结构体字段 : 通过 . 访问,如果结构体某个字段是引用类型,引用类型必须要初始化
	var person Person = Person{name: "Dream", age: 18}
	fmt.Println("访问 结构体 person 的值 :>>>> ", person.name)
	fmt.Println("访问 结构体 person 的值 :>>>> ", person.age)

	//访问 结构体 person 的值 :>>>>  Dream
	//访问 结构体 person 的值 :>>>>  18
}

【9】匿名字段

package main

import "fmt"

func main() {
	// 匿名字段 ----> 字段没有名字 ----> 匿名字段类型不能重复 , 可以和有名字段混合使用
	type Animal struct {
		string // 定义了字段类型,但是字段没有名字
		int    // 类型名就是字段名
	}

	// 初始化结构体
	// 按位置赋初值
	var animal Animal = Animal{"梦梦", 18}
	fmt.Println("这是初始化后的 结构体 animal :>>>> ", animal) // 这是初始化后的 结构体 animal :>>>>  {梦梦 18}
	// 按关键字赋初值 -- 如果没有字段名,则类型名就是字段名
	var animal1 Animal = Animal{string: "蚩梦", int: 19}
	fmt.Println("这是初始化后的 结构体 animal1 :>>>> ", animal1) // 这是初始化后的 结构体 animal1 :>>>>  {蚩梦 19}

	// 字段取值
	fmt.Println("结构体 animal 的取值 :>>>> ", animal1.string) // 结构体 animal 的取值 :>>>>  蚩梦
}

【10】结构体嵌套

(1)结构体嵌套有名结构体

package main

import "fmt"

func main() {
	// 结构体嵌套 --- 结构体中套结构体
	type Duck struct {
		name string
		age  int
	}
	type Chick struct {
		name   string
		height float32
		duck   Duck // 可以是自己定义的结构体
	}

	// 结构体初始化
	var chick Chick = Chick{name: "战斗鸡", height: 88, duck: Duck{name: "战斗鸭", age: 99}}
	fmt.Println("这是结构体初始化后的 chick :>>>> ", chick) // 这是结构体初始化后的 chick :>>>>  {战斗鸡 88 {战斗鸭 99}}

	// 结构体的取值
	fmt.Println("这是结构体的取值 :>>>> ", chick.duck.name) // 这是结构体的取值 :>>>>  战斗鸭
}

(2)结构体嵌套匿名结构体

package main

import "fmt"

func main() {
	// 结构体嵌套 --- 结构体中套结构体
	type Chick struct {
		name   string
		height float32

		// 可以是自己定义的结构体 --- 匿名结构体
		duck struct {
			name string
			age  int
		}
	}

	// 结构体初始化
	var chick Chick = Chick{name: "战斗鸡", height: 88}
	chick.duck.name = "战斗鸭"
	chick.duck.age = 66
	fmt.Println("这是结构体初始化后的 chick :>>>> ", chick) // 这是结构体初始化后的 chick :>>>>  {战斗鸡 88 {战斗鸭 66}}
}

【11】字段提升

package main

import "fmt"

func main() {
	// 结构体嵌套 + 匿名字段 --- 字段提升
	type Duck struct {
		name string
		age  int
	}

	type Chick struct {
		name   string
		height float32
		Duck
	}

	// 结构体初始化
	var chick Chick = Chick{name: "战斗鸡", height: 88, Duck: Duck{name: "战斗鸭", age: 66}}
	fmt.Println("这是结构体初始化后的 chick :>>>> ", chick) // 这是结构体初始化后的 chick :>>>>  {战斗鸡 88 {战斗鸭 66}}

	// 取值
	fmt.Println("取出内层结构体的值  :>>>> ", chick.Duck.name)
	// 字段提升,本来是 Duck 的字段,但是提升到了 Chick 身上
	// 但是字段名重复就不能再提升了
	fmt.Println("取出内层结构体的值  :>>>> ", chick.name)

	// 字段提升类似于面向对象的继承 ----> 相当于 Chick 继承了 Duck ----> 子类中调用父类的属性 ----> 子类对象.属性名

	// Chick 重写了 Duck 的name字段,所以在取值时,自己有就先用自己的,自己没有再用继承的

	// 在子类中使用父类的属性 super().属性名 ----> 对应到 Go 语言中,就是 父类名.属性名 (Duck.name)

}

【12】导出结构体和字段

(1)定义包

  • nature/Animal.go
package nature

type Animal struct {
	name string
	Age  int // 变量名首字母大写开头,表示导出字段 ----> 可以在外部包使用
}

(2)外部导出包内部字段

package main

import (
	"Project1/day03/nature"
	"fmt"
)

func main() {
	// 导出结构体字段 ----> 包名下 的变量首字母大写,表示导出,在其他包可以使用
	// 字段名和结构体名字规则一样,大写可以导出使用,小写只能包内部使用

	// Unexported field 'name' usage
	// 字段名首字母小写表示非导出字段,外部包不能使用
	// animal := nature.Animal{name: "小梦", Age: 18}
	// 字段名首字母大写则表示导出字段,外部可以使用
	animal := nature.Animal{Age: 18} // 不能按位置传参,只能按关键字传参

	fmt.Println("这是创建的 animal :>>>> ", animal) // 这是创建的 animal :>>>>  { 18}

}

【13】结构体相等性

(1)可以比较

  • 如果字段都是可比较的,那结构体可以比较
package main

import "fmt"

func main() {
	// 结构体相等性
	// 结构体是否能比较,取决于结构体字段
	// 如果字段都是可比较的,那结构体可以比较
	// 如果有不可以比较的字段,结构体就不可以比较
	
	type Animal struct {
		name string
		age  int
	}

	// 值类型 , 可以 == 比较 ; 引用类型只能 ==nil
	var a, b Animal = Animal{name: "梦梦", age: 18}, Animal{name: "梦梦", age: 18}
	fmt.Println("定义两个相同的机构体,并判断是否相等 :>>>> ", a == b) // 定义两个相同的机构体,并判断是否相等 :>>>>  true
}

(2)不可以比较

  • 如果有不可以比较的字段,结构体就不可以比较
package main

import "fmt"

func main() {
	// 结构体相等性
	// 结构体是否能比较,取决于结构体字段
	// 如果字段都是可比较的,那结构体可以比较
	// 如果有不可以比较的字段,结构体就不可以比较

	type Animal struct {
		name  *string
		age   int
		hobby []string // 切片类型不能比较
	}
	var x []int = []int{1, 2, 3}
	var y []int = []int{1, 2, 3}

	// 切片类型不能 == 比较
	fmt.Println("定义两个相同的机构体,并判断是否相等 :>>>> ", x == y) // Invalid operation: x == y (the operator == is not defined on []int)
}
package main

import "fmt"

func main() {
	// 结构体相等性
	// 结构体是否能比较,取决于结构体字段
	// 如果字段都是可比较的,那结构体可以比较
	// 如果有不可以比较的字段,结构体就不可以比较

	type Animal struct {
		name  *string
		age   int
		hobby []string // 切片类型不能比较
	}
	// 值类型 , 可以 == 比较 ; 引用类型只能 ==nil
	var a, b Animal = Animal{}, Animal{} // 初始化
	// 但是不能放在一起比较
	fmt.Println("定义两个相同的机构体,并判断是否相等 :>>>> ", a == b) //Invalid operation: a == b (the operator == is not defined on Animal)
}