结构体:
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合结构体可以很好的管理一批有联系的数据,使用结构体可以提高程序的易读性,类似于Java的类一样不能在结构体直接赋值字段名必须唯一,可用_
补位,支持使用自身类型的指针成员。字段名、排列顺序属类 型组成部分。除对齐处理外,编译器不会优化、调整内存布局。struct {}
是一个无元素的结构体类型,通常在没有信息存储时使用。优点是大小为0,不需要内存来存储struct {}类型的值struct {} {}
是一个复合字面量,它构造了一个struct {}类型的值,该值也是空
结构体的创建与初始化:
创建格式:
type 结构体名 struct {
成员名 数据类型
}
顺序初始化格式:
var 变量名 结构体名 = 结构体名{成员值1,成员值2}
部分初始化格式:
var 变量名 结构体名 = 结构体名{成员名:成员值,成员名:成员值}
通过结构体变量.成员方式初始化:
var 变量名 结构体名
变量名.成员名 = 成员值
演示:
// 结构体可以创建在函数外面,这样是全局的,如果是定义在函数的里面就是局部的
type Student struct {
// 成员名称不加var关键字
id int
name string
age int
addr string
}
func structDemo() {
// 顺序初始化
var s1 Student = Student{001, "itzhuzhu", 23, "广州"}
// 部分初始化
var s2 Student = Student{name: "itzhuzhu", age: 23}
//通过结构体变量.成员方式初始化
var stu Student
stu.name = "itzhuzhu"
stu.id = 23
fmt.Println(s1) //{1 itzhuzhu 2 广州}
fmt.Println(s2) //{0 itzhuzhu 23 }
fmt.Println(stu) //{23 itzhuzhu 0 }
}
仅在字段类型全部支持时,才可做相等 *** 作
func main() {
type data struct {
x int
y map[string]int
}
d1 := data{
x: 100,
}
d2 := data{
x: 100,
}
println(d1 == d2) // 无效 *** 作: struct containing map[string]int cannot be compared
}
可使用指针直接 *** 作结构字段,但不能是多级指针
func main() {
type user struct {
name string
age int
}
p := &user{
name: "Tom",
age: 20,
}
p.age++
p2 := &p
*p2.name = "Jack" // 报错: p2.name undefined (type **user has no field or method name)
}
空结构:
空结构struct{ }
是指没有字段的结构类型。它比较特殊,是因为无论是其自身,还是作为数组元素类型,其长度都为零
func main() {
var a struct{}
var b [100]struct{}
println(unsafe.Sizeof(a), unsafe.Sizeof(b))
}
输出:
0 0
尽管没有分配存储内存,但依然可以 *** 作数组元素,对应切片len、cap属性也正常
func main() {
var d [100]struct{}
s := d[:]
d[1] = struct{}{}
s[2] = struct{}{}
fmt.Println(s[3], len(s), cap(s))
}
输出:
{} 100 100
空结构对象可用于channel作为事件通知
func main() {
exit := make(chan struct{})
go func() {
println("hello, world!")
exit <- struct{}{}
}()
<-exit
println("end.")
}
匿名字段:
所谓匿名字段(anonymousfield),是指没有名字,仅有类型的字段。也被称作嵌入字段或嵌入类型。
type attr struct {
perm int
}
type file struct {
name string
attr // 仅有类型名
}
从编译器角度看,这只是隐式以类型标识作名字的字段。相当于语法糖,可实现面向对象 语言中引用基类成员的使用方法
func main() {
type attr struct {
age int
}
type file struct {
name string
attr
}
f := file{"itzhuzhu", attr{24}}
f = file{
name: "itzhuzhu",
attr: attr{
age: 24,
},
}
f.age = 24 //设置匿名字段成员
println(f.age) //读取匿名字段成员
}
对于其他包里的类型,隐式字段名字不包括包名称
func main() {
type data struct {
os.File
}
d := data{
File: os.File{}, // 注意区分字段名字和初始化成员对象的差别
}
fmt.Printf("%#v\n", d)
}
不仅仅是结构体,除接口指针和多级指针以外的任何命名类型都可作为匿名字段
func main() {
type data struct {
*int // 嵌入指针类型
string
}
x := 100
d := data{
int: &x, // 使用基础类型作为字段名
string: "abc",
}
fmt.Printf("%#v\n", d)
}
未命名类型没有名字标识,自然无法提供隐式字段名
type a *int
type b **int
type c interface{}
type d struct {
*a // 错误: embedded type cannot be a pointer
b // 错误: embedded type cannot be a pointer
*c // 错误: embedded type cannot be a pointer to interface
}
不能将基础类型和其指针类型同时嵌入,因为两者字段名字相同
type data struct {
*int
int // 错误: duplicate field int
}
虽然可以像普通字段那样访问匿名字段成员,但会存在重名问题。默认情况下,编译器从 当前显式命名字段开始,逐步向内查找匿名字段成员。如匿名字段成员被外层同名字段遮蔽,那么必须使用显式字段名
func main() {
type file struct {
name string
}
type data struct {
file
name string // 与匿名字段 file.name 重名
}
d := data{
name: "data",
file: file{"file"},
}
d.name = "data2" // 访问 data.name
d.file.name = "file2" // 使用显式字段名访问data.file.name
fmt.Println(d.name, d.file.name)
fmt.Printf("%#v\n", d)
}
如果多个相同层次的匿名字段成员重名,就只能使用显式字段名访问,因为编译器无法确 认正确目标
func main() {
type file struct {
name string
}
type log struct {
name string
}
type data struct {
file // file和log层次相同
log // file.name 和 log.name 重名
}
d := data{}
d.name = "name" // 错误:ambiguous selector d.name
d.file.name = "file"
d.log.name = "log"
}
严格来说,Go并不是传统意义上的面向对象编程语言。匿名字段不是继承机制,也无法 做多态处理。虽然配合方法集,可以用接口来实现一些类似的调用 *** 作,但其本质是完全不同的
结构体与数组:
结构体数组定义:
// 数组需要要指定长度
var 结构体数组名[索引] 结构体类型 = [索引] 结构体类型{}
数组修改结构体成员的值:
结构体数组名[索引].成员=值
演示:
func structDemo02() {
var arr [3]Student = [3]Student{
Student{001, "itzhuzhu", 23, "广州"},
Student{002, "itzhuzhu2", 23, "广州2"},
Student{003, "itzhuzhu3", 23, "广州3"},
}
fmt.Println(arr) //[{1 itzhuzhu 23 广州} {2 itzhuzhu2 23 广州2} {3 itzhuzhu3 23 广州3}]
fmt.Println(arr[0]) //{1 itzhuzhu 23 广州}
fmt.Println(arr[0].name, arr[0].age) //itzhuzhu 23
//结构体数组名[索引].成员=值
arr[0].addr = "深圳"
fmt.Println("成员值修改后", arr[0]) //{1 itzhuzhu 23 深圳}
// 通过循环输出结构体数组中的内容
for i := 0; i < len(arr); i++ {
fmt.Println("通过循环输出结构体数组全部的内容:", arr[i])
fmt.Println("通过循环输出结构体数组指定的内容:", arr[i].id)
}
for key, value := range arr {
fmt.Println("key:", key)
fmt.Println("value:", value)
fmt.Println("打印指定内容:", value.age)
}
}
结构体与切片:
和数组的玩法一样,除了定义格式稍微不同
结构体切片定义:
var 结构体数组名[] 结构体类型 = [] 结构体类型{}
切片修改结构体成员的值:
结构体切片名[索引].成员=值
演示:
func structDemo03() {
var arr []Student = []Student{
Student{001, "itzhuzhu", 23, "广州"},
Student{002, "itzhuzhu2", 23, "广州2"},
Student{003, "itzhuzhu3", 23, "广州3"},
}
fmt.Println(arr) //[{1 itzhuzhu 23 广州} {2 itzhuzhu2 23 广州2} {3 itzhuzhu3 23 广州3}]
fmt.Println(arr[0]) //{1 itzhuzhu 23 广州}
fmt.Println(arr[0].name, arr[0].age) //itzhuzhu 23
//结构体数组名[索引].成员=值
arr[0].addr = "深圳"
fmt.Println("成员值修改后", arr[0]) //{1 itzhuzhu 23 深圳}
// 通过循环输出结构体数组中的内容
for i := 0; i < len(arr); i++ {
fmt.Println("通过循环输出结构体切片全部的内容:", arr[i])
fmt.Println("通过循环输出结构体切片指定的内容:", arr[i].id)
}
for key, value := range arr {
fmt.Println("key:", key)
fmt.Println("value:", value)
fmt.Println("打印指定内容:", value.age)
}
// 追加数据
arr = append(arr, Student{004, "itzhuzhu4", 23, "佛山"})
fmt.Println(arr)
}
结构体与map:
结构体map的定义:
make(map[key的类型]value的类型)
演示:
func structDemo04() {
m := make(map[int]Student)
m[0] = Student{001, "itzhuzhu", 23, "广州"}
m[1] = Student{002, "itzhuzhu2", 23, "广州"}
m[2] = Student{003, "itzhuzhu3", 23, "广州"}
fmt.Println("全部数据:", m)
fmt.Println("指定索引数据:", m[0])
fmt.Println("指定索引成员数据:", m[0].name)
delete(m, 0)
for key, value := range m {
fmt.Println("key:", key)
fmt.Println("value:", value)
fmt.Println("value.name:", value.name)
fmt.Println("value.name:", value.name)
}
}
结构体作为函数参数:
在函数中修改结构体成员值,是不会影响到原结构体的
创建格式:
func 函数名(结构体){
函数体
}
调用格式:
函数名(结构体)
演示:
func main() {
stu := Student{001, "itzhuzhu", 23, "广州"}
structDemo05(stu)
fmt.Println(stu)
}
func structDemo05(stu Student) {
// 修改结构体的成员,不会影响到原结构体
stu.id = 999
fmt.Println(stu)
}
求最大学生年龄:
键盘录入学生信息,然后判断年龄最大的学生,打印出年龄最大的学生信息
演示:
func main() {
stu := make([]Student, 3)
structDemo06(stu)
}
func structDemo06(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].id, &stu[i].name, &stu[i].age, &stu[i].addr)
// 如果大于最大年龄就重新赋值,然后把最大年龄的学生索引给定义的变量赋值
if stu[i].age > max {
max = stu[i].age
maxIndex = i
}
}
fmt.Println(stu[maxIndex])
}
结构体添加方法:
主要结构体类型不一样,方法是可以重名的,算是不同的方法
一般写方法的时候都会将结构体类型写为指针类型的
格式:
// 表示是为某一个结构体添加的方法
func (结构体简称 结构体)方法名 (参数列表)(返回值列表){
代码
}
调用格式:
对象名.方法
演示:
func main() {
stu := Student{"itzhuzhu", 24, 100}
stu.show() //{itzhuzhu 222 100}
fmt.Println(stu) //{itzhuzhu 24 100}
}
type Student struct {
name string
age int
score float64
}
// 普通方法是不会改变原来结构体的值的,除非是指针类型
func (student Student) show() {
student.age = 222
fmt.Println(student)
}
// 改成指针类型就会将person的数据改变
func (person *Person) show() {
person.age = 222
fmt.Println(person)
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)