Go_结构体与数组、切片、map、方法、作为函数参数、type、Tag

结构体:

  • 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合,可以提高程序维护性、易读性,相当于Java中的class
  • 结构体内成员名必须唯一,可用_补位,支持使用自身类型的指针成员。
  • struct {}是一个无元素的结构体类型,通常在没有信息存储时使用,优点是大小为0
  • 结构体是值类型,结构体是一种数据类型

结构体的创建与初始化:

创建格式:

type 结构体名 struct {
	成员名 数据类型
}

实例化

实例化是指在面向对象的编程中,把用类创建对象的过程称为实例化。是将一个抽象的概念类具体到该类实物的过程。

变量名 := 结构体名{}

顺序初始化:

// 这种方式必须将全部成员初始化s
变量名 := 结构体名{成员值1,成员值2}

逐个初始化:部分初始化也是一样,只写要初始化的成员名即可

变量名 := 结构体名{
	成员名:成员值,
	成员名:成员值, // 要加逗号,最后一个也要加逗号
}

变量.成员名初始化1:

	var 变量名 结构体名
	变量名.成员名 = 成员值

变量.成员名初始化2:

	变量名 := 结构体名2{}
	变量名.成员名 = 成员值

new方法初始化:

var student *Student = new(Student)
// 可简写为
student := new(Student)
// 可省略*号,Go语言会自动加上
// 指针方式赋值
(*student).Name = "韩信"

演示:

// 结构体创建在函数外面,这样是全局的,如果是定义在函数的里面就是局部的
type Hero struct {
	Name     string
	Location string
	Gold     int64
}

func main() {
	// 逐个初始化、部分初始化
	h1 := Hero{Name: "韩信", Location: "打野", Gold: 300}
	fmt.Println(h1)

	// 顺序初始化要按照成员顺序全部赋值,否则数据类型不一致会报错
	h2 := Hero{"李白", "打野", 300}
	fmt.Println(h2)

	// 变量.成员名方式1
	var h3 Hero
	h3.Name = "露娜"
	h3.Location = "打野"
	h3.Gold = 300

	// 变量.成员名方式2
	h4 := Hero{}
	h4.Name = "不知火舞"
	h4.Location = "中路"
	h4.Gold = 300

	// new方法初始化1
	var h5 *Hero = new(Hero)
	fmt.Println("new方法初始化1:", h5)

	// new方法初始化-简写
	h6 := new(Hero)
	(*h6).Name = "干将莫邪"
	// 也可以直接去掉星号,编译期间会自动加上*
	h6.Location = "中路"
	fmt.Println("new方法初始化,简写版:", h6)

	// new方法初始化2
	var h7 = &Hero{}
	h7.Name = "干将莫邪"
	h7.Location = "中路"
	h7.Gold = 300
	fmt.Println(*h7)

	// 星号运算,下面这么写会报错, 点的运算大于 *号,相当于*(h7.Name),但这是错误的语法
	//fmt.Println(*h7.Name)   // 错误写法
	fmt.Println((*h7).Name) // 正确写法
}

结构体的所有字段的内存地址在内存中是连续分布的,这样的话,我们可以通过地址的加减来快速找到下一个字段

type Test struct {
	a, b, c, d int
}

func main() {
	test := Test{1, 2, 3, 4}
	fmt.Printf("a的地址是:%p \n", &test.a)
	fmt.Printf("b的地址是:%p \n", &test.b)
	fmt.Printf("c的地址是:%p \n", &test.c)
	fmt.Printf("d的地址是:%p \n", &test.d)
}

输出:因为int类型是占8个字节,所以每个变量的内存地址依次加8,而内存地址都是16进制计算的,所以结尾都是0和8(8+8=16 逢16进就是0)

a的地址是:0x1400012c000
b的地址是:0x1400012c008
c的地址是:0x1400012c010
d的地址是:0x1400012c018

如果是指针类型,那么对指针来说,指针本身有个地址,同时也指向另一块地址,那么此时指向的地址不一定是连续的,但他们本身的地址是连续的

func main() {
	a := 10
	b := 20
	c := 30
	d := 40

	w := &a
	x := &b
	y := &c
	z := &d

	fmt.Printf("指针w的地址是:%p \n", &w)
	fmt.Printf("指针x的地址是:%p \n", &x)
	fmt.Printf("指针y的地址是:%p \n", &y)
	fmt.Printf("指针z的地址是:%p \n", &z)
}

输出:

指针w的地址是:0x14000114018 
指针x的地址是:0x14000114020 
指针y的地址是:0x14000114028 
指针z的地址是:0x14000114030 

结构体字段数据类型如果是:指针、slice、map那么默认值都为nil,还没有分配空间,需要先make才可以使用

type Test struct {
	name   string
	age    int
	height float64
	ptr    *int           // 指针
	slice  []int          // 切片
	m      map[string]int // map
}

func main() {
	t := Test{}
	fmt.Println(t.name)
	fmt.Println(t.age)
	fmt.Println(t.height)
	fmt.Println(t.ptr)
	fmt.Println(t.slice)
	fmt.Println(t.m)
}

输出:

0
0
<nil>
[]
map[]

结构体在字段类型全部赋值时,才可做相等操作

type data struct {
	x int
	y map[string]int
}

type Student struct {
	name string
	age  int
}

type A struct {
	num int
}

type B struct {
	num int
}

func main() {
	d1 := data{
		x: 100,
	}
	d2 := data{
		x: 100,
	}
	//d1和d2是两个变量,就算是都是同一个d1也无法比较,因为data中还有一个y没有被赋值
	println(d1 == d2) //  无效运算: d1 == d2 (在 data 中未定义运算符 ==)

	// 两个结构体变量可以使用==或!=运算,但不支持<、>
	s1 := Student{name: "itzhuzhu", age: 24}
	s2 := Student{name: "韩信", age: 84}
	s3 := Student{name: "itzhuzhu", age: 24}

	fmt.Println("s1 == s3", s1 == s3) // true
	fmt.Println("s1 == s2", s1 == s2) // false

	// 同类型的两个结构体变量可以相互赋值
	var s4 Student
	s4 = s3
	fmt.Println("s4", s4) // s4 {itzhuzhu 24}

	var a A
	var b B
  
	// 两个结构体之间相互转换必须是字段的名字、个数、数据类型都一致,否则报错
	a = A(b)
	fmt.Println(a, b) // {0} {0}
}

可使用指针直接操作结构字段,但不能是多级指针,同样可以对结构体取地址

type user struct {
	name string
	age  int
}

func main() {
	p := &user{
		name: "itzhuzhu",
		age:  20,
	}

	fmt.Println("++前 结构体的成员值:", p) 
	p.age++
	fmt.Println("++后 结构体的成员值:", p) 
  
	// 结构体不能直接 &user{}取地址,需要有一个变量接收才可以
	fmt.Println("结构体user的地址:", &p) // 结构体user的地址: 0x14000124018

	p2 := &p
	*p2.name = "Jack" // 报错:'p2.name' (类型 'string')的间接引用无效
}

输出:

++前 结构体的成员值: &{itzhuzhu 20}
++后 结构体的成员值: &{itzhuzhu 21}
结构体user的地址: 0x14000114018

type-自定义类型:

使用关键字type定义用于自定义类型,包括基于现有类型创建、结构体、数据类型。byte、rune的源码中就是使用了类型别名,实际上使用byte和使用uint8没有什么区别,相当于起了个外号

type byte = uint8

// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32

结构体使用type重新定义后Go会认为是新的数据类型,支持强制转换。

type Student struct {
	name string
}

type Stu Student
type integer int // 相当于给int取别名为integer了

func main() {
	var stu1 Student
	var stu2 Stu

	stu1 = stu2               // 报错:无法将 'stu2' (类型 Stu) 用作类型 Student
	stu1 = Student(stu2)      // 支持使用目标类型强制转换
	fmt.Println(stu1 == stu2) // 即使是判断是否相等也是错误的,无效运算: stu1 == stu2(类型 Student 和 Stu 不匹配)
  
  var i integer = 10
	var j int = 10
	i = j          // 报错:无法将 'j' (类型 int) 用作类型 integer
	i = integer(j) // 因为是两种数据类型,所以不支持赋值,只能强制转换
}

多个type定义可合并成组,也可在函数或代码块内定义局部类型

type (
	user struct { // 结构体
		name string
		age  uint
	}

	event func(string) // 函数类型
)

func main() {
	u := user{"itzhuzhu", 24}
	fmt.Println(u)

	var f event = func(s string) {
		fmt.Println(s)
	}

	f("haha")
}

Tag:

  • Tag是一个字符串,以key、value形式存在,用于标记字段说明,可以配合反射使用,以及Json解析。
  • Tag是用来对字段进行描述的元数据。尽管它不属于数据成员,但却是类型的组成部分。
  • 在运行期,可用反射获取标签信息。常被用作格式校验,数据库关系映射等
    • key:不能为空,不能包含、空格、引号、冒号
    • value:使用双引号

获取结构体Tag内容:

type User struct { // 结构体
	name string
	age  uint
}

func main() {
	u := User{}
	// 获得任意值类型对象
	user := reflect.TypeOf(u)

	//Field:获取字段   参数表示第几个字段
	field := user.Field(0)

	// Tag:获取标签
	fmt.Println(field.Tag.Get("name"))

	// 获取键值对 key:value
	field2 := user.Field(1)
	fmt.Println(field2.Tag.Get("age"))
}

输出:

userName
userAge

空结构:

空结构struct{ }是指没有字段的结构类型。它比较特殊,无论是其自身,还是作为数组元素类型,其长度都为零

func main() {
  var a struct{}
	var b [100]struct{}
  // 返回操作数在内存中的字节大小
	fmt.Println(unsafe.Sizeof(a), unsafe.Sizeof(b)) // 0	0
}

unsafe.Sizeof可以打印出变量的数据类型字节长度

type Student struct {
	age  int // 8个字节
	name string // 16个字节
}

func main() {
	stu := Student{}
	stu.age = 23
	stu.name = "itzhuzhu"
	fmt.Println(unsafe.Sizeof(stu.age))  //8
	fmt.Println(unsafe.Sizeof(stu.name)) //16
  fmt.Println(unsafe.Sizeof(stu)) // 24
}

空结构对象可用于channel作为事件通知

func main() {
	exit := make(chan struct{})
	go func() {
		fmt.Println("hello, world!")
		exit <- struct{}{}
	}()
	<-exit
	fmt.Println("end.")
}

匿名字段:

所谓匿名字段是指没有名字,仅有类型的字段

type file struct {
	name string
	int // 仅有类型
}

从编译器角度看,这只是隐式以类型标识作名字的字段。相当于语法糖,可实现面向对象语言中引用基类成员的使用方法

type attr struct {
	age int
}
type file struct {
	name string
	attr
}

func main() {
	f := file{"itzhuzhu", attr{24}}

	// 或
	f = file{
		name: "itzhuzhu",
		attr: attr{
			age: 26,
		},
	}
	fmt.Println(f.age) //读取匿名字段成员
}

结构体与数组:

结构体也可以作为数组的数据类型使用

结构体数组定义:

// 数组需要要指定长度
数组名:= [长度] 结构体类型{}

演示:

type Student struct {
	name string
	age  int
	addr string
}

func main() {
	// 定义一个长度为3的数组,数据类型是结构体
	arr := [3]Student{
		Student{"韩信", 23, "广州"},
		// Student可以省略
		{"李白", 23, "深圳"},
		{"露娜", 23, "佛山"},
	}
	fmt.Println("arr:", arr)
	fmt.Println("arr[0]:", arr[0])
	fmt.Println("arr[0].name:", arr[0].name)

	arr[0].addr = "深圳"
	fmt.Println("成员值修改后", arr[0])

	// 通过循环输出结构体数组中的内容
	for i := 0; i < len(arr); i++ {
		fmt.Println("通过循环输出结构体数组全部的内容:", arr[i])
		fmt.Println("通过循环输出结构体数组指定的内容:", arr[i].name)
	}

	for key, value := range arr {
		fmt.Println("key:", key)
		fmt.Println("value:", value)
		fmt.Println("打印指定内容:", value.age)
	}
}

结构体与切片:

和数组的玩法一样,除了定义格式稍微不同

结构体切片定义:

var 结构体数组名[] 结构体类型 = [] 结构体类型{}

切片修改结构体成员的值:

结构体切片名[索引].成员=

演示:

type Student struct {
	name string
	age  int
	addr string
}

func main() {
	// 与结构体数组不同的是,切片不用写长度了
	arr := []Student{
		{"韩信", 23, "广州"},
		{"李白", 23, "深圳"},
		{"露娜", 23, "佛山"},
	}
	fmt.Println("arr:", arr)
	fmt.Println("arr[0]:", arr[0])
	fmt.Println("arr[0].name:", arr[0].name)

	arr[0].addr = "深圳"
	fmt.Println("成员值修改后", arr[0])

	// 通过循环输出结构体数组中的内容
	for i := 0; i < len(arr); i++ {
		fmt.Println("通过循环输出结构体数组全部的内容:", arr[i])
		fmt.Println("通过循环输出结构体数组指定的内容:", arr[i].name)
	}

	for key, value := range arr {
		fmt.Println("key:", key)
		fmt.Println("value:", value)
		fmt.Println("打印指定内容:", value.age)
	}

	// 追加数据
	arr = append(arr, Student{"干将莫邪", 56, "峡谷"})
	fmt.Println("追加数据后:", arr)
}

结构体与map:

结构体map的定义:

make(map[key的类型]value的类型)

演示:

type Student struct {
	name string
	age  int
	addr string
}

func main() {
	m := make(map[int]Student)
	m[0] = Student{"韩信", 23, "广州"}
	m[1] = Student{"李白", 24, "广州"}
	m[2] = Student{"露娜", 25, "广州"}

	fmt.Println("全部数据:", m)
	fmt.Println("指定索引数据:", m[0])
	fmt.Println("指定索引成员数据:", m[0].name)

	delete(m, 0)
	fmt.Println("删除后:", m)

	// 循环打印
	for key, value := range m {
		fmt.Println("key:", key)
		fmt.Println("value:", value)
		fmt.Println("value.name:", value.name)
		fmt.Println("value.name:", value.age)
	}
}

结构体作为函数参数:

在函数中修改结构体成员值,是不会影响到原结构体的

创建格式:

func 函数名(结构体){
	函数体
}

调用格式:

函数名(结构体)

演示:

type Student struct {
	name string
	age  int
	addr string
}

func main() {
	stu := Student{"不知火舞", 23, "广州"}
	test(stu)
	fmt.Println("调用后:", stu)
}

func test(stu Student) {
	// 修改结构体的成员,不会影响到原结构体
	stu.age = 999
	fmt.Println("修改后:", stu)
}

输出:

修改后: {不知火舞 999 广州}
调用后: {不知火舞 23 广州}

结构体添加方法:

主要结构体类型不一样,方法是可以重名的,算是不同的方法
一般写方法的时候都会将结构体类型写为指针类型的

格式:

// 表示是为某一个结构体添加的方法
func (变量名 数据类型)方法名 (参数列表)(返回值列表){
	代码
}

调用格式:

对象名.方法

演示:

func main() {
	stu := Student{"itzhuzhu", 24, 100}
	stu.show()
	fmt.Println("调用show()后:", stu)

	stu.show2()
	fmt.Println("调用show2()后:", stu)
}

type Student struct {
	name  string
	age   int
	score float64
}

// 普通方法是不会改变原来结构体的值的,除非是指针类型
func (s Student) show() {
	s.age = 222
}

// 改成指针类型就会将person的数据改变
func (s *Student) show2() {
	s.age = 333
}

输出:

调用show()后: {itzhuzhu 24 100}
调用show2()后: {itzhuzhu 333 100}

求最大学生年龄:

键盘录入学生信息,然后判断年龄最大的学生,打印出年龄最大的学生信息

type Student struct {
	name string
	age  int
	addr string
}

func main() {
	stu := make([]Student, 3)
	test(stu)
}

func test(stu []Student) {
	//定义年龄最大的变量
	max := stu[0].age

	//定义年龄最大的学生索引
	var maxIndex int

	for i := 0; i < len(stu); i++ {
		fmt.Printf("请输入第%d个学生的详细信息\n", i+1)
		//切片是Student的类型,Student中有多个成员,所以要把里面的每个成员都赋值
		fmt.Scan(&stu[i].name, &stu[i].age, &stu[i].addr)

		// 如果大于最大年龄就重新赋值,然后把最大年龄的学生索引给定义的变量赋值
		if stu[i].age > max {
			max = stu[i].age
			maxIndex = i
		}
	}
	fmt.Println(stu[maxIndex])
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

itzhuzhu.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值