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

结构体:

  • 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合
  • 结构体可以很好的管理数据,使用结构体可以提高程序的易读性,类似于Java的类一样
  • 结构体内成员名必须唯一,可用_补位,支持使用自身类型的指针成员。
  • struct {}是一个无元素的结构体类型,通常在没有信息存储时使用。优点是大小为0,不需要内存来存储struct {}类型的值
  • struct {} {}是一个复合字面量,它构造了一个struct {}类型的值,该值也是空
  • 结构体是值类型,结构体是一种数据类型

结构体的创建与初始化:

创建格式:

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

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

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

顺序初始化:

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

变量.成员名初始化1:

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

变量.成员名初始化2:

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

new方法初始化:

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

演示:

// 结构体创建在函数外面,这样是全局的,如果是定义在函数的里面就是局部的
type Hero struct {Name     stringLocation stringGold     int64
}func main() {// 逐个初始化、部分初始化h1 := Hero{Name: "韩信", Location: "打野", Gold: 300}fmt.Println(h1)// 顺序初始化要按照成员顺序全部赋值,否则数据类型不一致报错h2 := Hero{"李白", "打野", 300}fmt.Println(h2)// 变量.成员名方式1var h3 Heroh3.Name = "露娜"h3.Location = "打野"h3.Gold = 300// 变量.成员名方式2h4 := Hero{}h4.Name = "不知火舞"h4.Location = "中路"h4.Gold = 300// new方法初始化1var h5 *Hero = new(Hero)fmt.Println(h5)// 简写为h6 := new(Hero)(*h6).Name = "干将莫邪"(*h6).Location = "中路"// 也可以直接去掉星号,编译期间会自动加上*fmt.Println(h6)// new方法初始化2var h7 = &Hero{}(*h7).Name = "干将莫邪"(*h7).Location = "中路"h7.Gold = 300fmt.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 := 10b := 20c := 30d := 40w := &ax := &by := &cz := &dfmt.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   stringage    intheight float64ptr    *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 inty map[string]int
}type Student struct {name stringage  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) // truefmt.Println("s1 == s2", s1 == s2) // false// 同类型的两个结构体变量可以相互赋值var s4 Students4 = s3fmt.Println("s4", s4) // s4 {itzhuzhu 24}var a Avar b B// 两个结构体之间相互转换必须是字段的名字、个数、数据类型都一致,否则报错a = A(b)fmt.Println(a, b) // {0} {0}
}

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

type user struct {name stringage  int
}func main() {p := &user{name: "itzhuzhu",age:  20,}fmt.Println("++前 结构体的成员值:", p) p.age++fmt.Println("++后 结构体的成员值:", p) // 结构体不能直接 &user{}取地址,需要有一个变量接收才可以fmt.Println("结构体user的地址:", &p) // 结构体user的地址: 0x14000124018p2 := &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 Studentvar stu2 Stustu1 = stu2               // 报错:无法将 'stu2' (类型 Stu) 用作类型 Studentstu1 = Student(stu2)      // 支持使用目标类型强制转换fmt.Println(stu1 == stu2) // 即使是判断是否相等也是错误的,无效运算: stu1 == stu2(类型 Student 和 Stu 不匹配)var i integer = 10var j int = 10i = j          // 报错:无法将 'j' (类型 int) 用作类型 integeri = integer(j) // 因为是两种数据类型,所以不支持赋值,只能强制转换
}

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

type (user struct { // 结构体name stringage  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 {// tag使用反斜杠标注name string `name:"userName"`age  int    `age:"userAge"`
}func main() {u := User{}user := reflect.TypeOf(u)field := user.Field(0)fmt.Println(field.Tag.Get("name"))field2 := user.Field(1)fmt.Println(field2.Tag.Get("age"))
}

空结构:

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

func main() {var a struct{}var b [100]struct{}fmt.Println(unsafe.Sizeof(a), unsafe.Sizeof(b)) // 0	0
}

尽管没有分配存储内存,但依然可以操作数组元素,对应切片len、cap属性也正常

var a struct{}
var b [100]struct{}func main() {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 = 23stu.name = "itzhuzhu"fmt.Println(unsafe.Sizeof(stu.age))  //8fmt.Println(unsafe.Sizeof(stu.name)) //16fmt.Println(unsafe.Sizeof(stu)) // 24
}

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

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

匿名字段:

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

type attr struct {perm int
}
type file struct {name stringint // 仅有类型
}

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

type attr struct {age int
}
type file struct {name stringattr
}func main() {f := file{"itzhuzhu", attr{24}}f = file{name: "itzhuzhu",attr: attr{age: 24,},}f.age = 24     //设置匿名字段成员println(f.age) //读取匿名字段成员
}

结构体与数组:

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

结构体数组定义:

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

数组修改结构体成员的值:

结构体数组名[索引].成员=

演示:

type Student struct {name stringage  intaddr string
}func main() {// 定义一个长度为3的数组,数据类型是结构体var arr [3]Student = [3]Student{Student{"韩信", 23, "广州"},Student{"李白", 23, "深圳"},Student{"露娜", 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 stringage  intaddr string
}func main() {// 与结构体数组不同的是,切片不用写长度了var arr []Student = []Student{Student{"韩信", 23, "广州"},Student{"李白", 23, "深圳"},Student{"露娜", 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 stringage  intaddr 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 stringage  intaddr string
}func main() {stu := Student{"不知火舞", 23, "广州"}test(stu)fmt.Println("调用后:", stu)
}func test(stu Student) {// 修改结构体的成员,不会影响到原结构体stu.age = 999fmt.Println("修改后:", stu)
}

输出:

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

求最大学生年龄:

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

type Student struct {name stringage  intaddr string
}func main() {stu := make([]Student, 3)test(stu)
}func test(stu []Student) {//定义年龄最大的变量max := stu[0].age//定义年龄最大的学生索引var maxIndex intfor 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].agemaxIndex = i}}fmt.Println(stu[maxIndex])
}

结构体添加方法:

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

格式:

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

调用格式:

对象名.方法

演示:

func main() {stu := Student{"itzhuzhu", 24, 100}stu.show()fmt.Println("调用show()后:", stu)stu.show2()fmt.Println("调用show2()后:", stu)
}type Student struct {name  stringage   intscore float64
}// 普通方法是不会改变原来结构体的值的,除非是指针类型
func (student Student) show() {student.age = 222fmt.Println(student)
}// 改成指针类型就会将person的数据改变
func (student *Student) show2() {student.age = 333fmt.Println(student)
}

输出:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/572109.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue项目中如何引入ElementUI

1.在cmd中输入命令&#xff1a; npm i element-ui -S 2.在src/main.js文件中增加代码&#xff1a; import ElementUI from element-ui import element-ui/lib/theme-default/index.cssVue.use(ElementUI) 转载于:https://www.cnblogs.com/xuemei/p/7372332.html

Golang——深浅拷贝

浅拷贝&#xff1a;只是拷贝了内存地址&#xff0c;会指向原来的内存&#xff0c;指向原来的内存地址&#xff0c;操作的时候会互相影响 go中的赋值、函数传参、函数返回值都是浅拷贝 深拷贝&#xff1a;会把变量的数据都拷贝一份&#xff0c;包括地址&#xff0c;新的内存地址…

[Java][web]利用Spring随时随地获得Request和Session

利用Spring随时随地获得Request和Session 一、准备工作&#xff1a; 在web.xml中加入 <listener> <listener-class> org.springframework.web.context.request.RequestContextListener</listener-class> </listener>二、用法&#xff1a;…

Golang实现通讯录小案例

代码&#xff1a; package mainimport "fmt"func main() {for {menu()} }type User struct {userName string/**key&#xff1a;表示电话的类型value&#xff1a;电话号码*/addressPhone map[string]string }// 定义切片 var userList make([]User, 0)func menu() …

MySql5.6版修改用户登录密码

1、使用 mysqladmin 方式: 打开命令提示符界面, 执行命令: mysqladmin -u root -p password 新密码 执行后提示输入旧密码完成密码修改, 当旧密码为空时直接按回车键确认即可。 2、UPDATE user 语句&#xff08;我自己用的就是这个方法&#xff09; 这种方式必须是先用root帐户…

Go_面向对象(抽象、封装、继承)

抽象 抽象是一种编程思维方式&#xff0c;是从多个事物中提取共性 例&#xff1a;产品经理和程序员都有工作的方法&#xff0c;但是工作内容不同&#xff0c;可以把工作抽象出来定义为一个方法&#xff0c;具体细节由调用者补充 银行存取款案例&#xff1a; 账号结构体取款方法…

Go_接口、多态、接口继承与转换、空接口、类型断言

接口 接口用于定义对象的行为&#xff0c;接口只指定对象应该做什么&#xff0c;实现这种行为由对象来决定。接口只是把所有具有共性的方法定义在一起&#xff0c;任何类型实现了接口中所有的方法&#xff0c;就是实现了这个接口。接口存在的意义就是用来定义规范&#xff0c;…

Discrete Logging POJ - 2417(BSGS)

Discrete Logging POJ - 2417 题意&#xff1a;给P&#xff0c;B&#xff0c;N&#xff0c;求最小的L使得 BL≡N (mod P)&#xff0c;其中P是素数。 Baby Step Giant Step 1 #include <cstdio>2 #include <cstring>3 #include <iostream>4 #include <cma…

Go_error处理及panic、recover使用的正确姿势

异常 异常就是程序出现了不正常的情况&#xff0c;会导致程序非正常停止&#xff0c;而异常处理就是针对非正常停止的情况&#xff0c;给出异常时的处理方式。语法错误不算异常体系中 error&#xff1a; error是一个接口&#xff0c;作用是返回程序异常的信息&#xff0c;err…

js 根据固定位置获取经纬度--腾讯地图

1.首先引入jq 和 腾讯地图js <script src"../js/jQuery.js"></script> <script charset"utf-8" src"http://map.qq.com/api/js?v2.exp"></script> 2.html代码部分 <body onload"init()"><button ty…

Go_文件/目录创建、写入、追加、读取、缓冲区、Stat、IsNotExist、Copy

操作目录 创建目录 Mkdir&#xff1a;创建单个目录MkdirAll&#xff1a;创建多级目录 func main() {err : os.Mkdir("/Users/itzhuzhu/Desktop/笔记/英语/a", os.ModePerm)if err ! nil {fmt.Println("创建失败", err)} else {fmt.Println("创建成…

【bzoj3343】教主的魔法 (分块 + 二分)

传送门(权限题) 题目分析 题意为&#xff1a;给定一个数列&#xff0c;修改和查询两种操作&#xff0c;修改每次给定一个区间&#xff0c;区间的所有元素都加上一个给定值&#xff0c;查询询问一段区间的数权值大于等于给定值的数有多少个。 首先对原序列分块&#xff0c;然后将…

Golang——string字符串常用函数(Contains、join、Index、Repeat、Replace、Split、Trim、Fields)

更多的还是去官方文档里去看&#xff1a;https://studygolang.com/pkgdoc Contains&#xff1a; 判断字符串中是否包含指定字符串 演示&#xff1a; func main() {str1 : "itzhuzhu"result : strings.Contains(str1, "zhu")fmt.Println(result) }join&a…

[flask 优化] 由flask-bootstrap,flask-moment引起的访问速度慢的原因及解决办法

一周时间快速阅读了400页的《javascript基础教程》&#xff0c;理解了主要概念。解决了一个很久之前的疑问。 我的网站是使用flask框架搭建的&#xff0c;介绍flask web的一本著名的书&#xff08;之前提到过&#xff09;作者搭建个人博客时&#xff0c;向读者推荐了flask-boot…

Go_关键字、编译、转义字符

关键字&#xff1a; 关键字是指被go语言赋予了特殊含义的单词&#xff0c;共25个&#xff0c;关键字不能用于自定义名字&#xff0c;只能在特定语法结构中使用。 breakdefaultfuncinterfaceselectcasedefergomapstructchanelsegotopackageswitchconstfallthroughifrangetypec…

NFS服务配置

NFS会经常用到&#xff0c;用于在网络上共享存储。举一个例子来说明一下NFS是用来做什么的。假如有三台机器A, B, C&#xff0c;它们需要访问同一个目录&#xff0c;目录中都是图片&#xff0c;传统的做法是把这些图片分别放到A, B, C. 但是使用NFS只需要放到A上&#xff0c;然…

Go_数据类型转换(Sprintf、Format、Append方式转换)

数据类型转换&#xff1a; 类型转换是将一种数据类型的变量转为另一种类型的变量Go强制要求使用显式类型转换。所以语法更能确定语句及表达式的明确含义转换的时候如果大的转给小的&#xff0c;会有精度损失&#xff08;数据溢出&#xff09;比如int64转int8 转换格式&#xff…

Mybayis的项目使用的Mapping文件使用总结参考(一)

作者:longgangbai 以前用过ibatis2,但是听说ibatis3有较大的性能提升&#xff0c;而且设计也更合理&#xff0c;他不兼容ibatis2.尽管ibatis3还是beta10的状态&#xff0c;但还是打算直接使用ibatis3.0, ibatis3.0应该更简单高效.最近还自己写了个ibatis3.0与spring集成的bean&…

并发编程概念、程序线程进程、线程同步、互斥量、读写锁、协程并发

多线程&#xff1a; 多线程就是同时执行多个应用程序&#xff0c;需要硬件的支持同时执行&#xff1a;不是某个时间段同时&#xff0c;cpu切换的比较快&#xff0c;所有用户会感觉是在同时运行 并发与并行&#xff1a; 并行(parallel)&#xff1a;指在同一时刻&#xff0c;有多…

第4阶段——制作根文件系统之分析init_post()如何启动第1个程序(1)

本章学习如何启动第一个应用程序 1.在前面的分析中我们了解到&#xff0c;在init进程中内核挂接到根文件系统之后&#xff0c;会开始启动第一个应用程序: kernel_init函数代码如下: static int __init kernel_init(void * unused) //进入init进程 …