Golang指针、Map和结构体-Golang夯实基础第四天
-
指针
指针是存储变量地址值的一种指针变量
指针的概念并非
Go
语言所独有的,C/C++
也有指针,但是Go
语言的指针,在正常情况下,是不允许编译和运算的。使用
&
取变量的地址值,使用*
取指针指向的地址值所存储的值,&
和*
是一对互补的操作符func Test_C_0(t *testing.T) { a := 100 b := &a c := *b fmt.Printf("%v,%p,%T\n", a, &a, a) fmt.Printf("%v,%p,%T\n", b, &b, b) fmt.Printf("%v,%p,%T\n", c, &c, c) }
100,0xc000018178,int 0xc000018178,0xc000012068,*int 100,0xc000018180,int
根据上面演示,他们每一个变量是类似这样子的:
-
作用
Go
函数的参数是值拷贝,也就是说,给函数传入一个值引用类型的变量a
,函数内部进行修改,执行完后,重新打印a
,a
的值不变func updateArr(arr [3]int) { arr[1] = 666 } func Test_C_1(t *testing.T) { a := [3]int{7, 8, 9} updateArr(a) fmt.Println(a) }
[7 8 9]
把参数修改为对应的指针类型就可以进行修改了
func updateArr2(arr *[3]int) { arr[1] = 666 } func Test_C_2(t *testing.T) { a := [3]int{7, 8, 9} updateArr2(&a) fmt.Println(a) }
[7 666 9]
特别需要注意的是,函数虽然是只能值拷贝,但是
Go
还有一种引用类型的变量,它们赋值时也是值拷贝(GO
语言中,只有值拷贝),只是拷贝是指针,通过下面的示例可以很清晰的理解和明白func Test_C_3(t *testing.T) { a := []int{7, 8, 9} b := a fmt.Printf("%v,%p,%p,%T\n", a, a, &a, a) fmt.Printf("%v,%p,%p,%T\n", b, b, &b, b) }
[7 8 9],0xc00001a120,0xc0000100c0,[]int [7 8 9],0xc00001a120,0xc0000100d8,[]int
-
空指针
一个指针没有被初始化分配变量,它的值就是空指针,就是
nil
。func Test_C_4(t *testing.T) { var a *int fmt.Println(a == nil) }
true
-
-
new
和make
new
和make
都是Go
的内置函数,之前有学习归纳过-
new
接受一个类型
T
,返回一个初始化好了的指针*T
,并且已经赋好对应的零值了func new(Type) *Type
func Test_C_5(t *testing.T) { var a *int = new(int) b := *a fmt.Println(a == nil) fmt.Printf("%v,%T\n", a, a) fmt.Printf("%v,%T\n", b, b) }
false 0xc000018178,*int 0,int
-
make
- 第一个参数接受一个类型
T
,且只能为slice
、map
、chan
这三种引用值类型的内存创建 - 第二个参数可以缺省,用于指定长度
- 对于
slice
来说,用于定义初始化时的赋的零值的个数 - 对于
map
来说,用于设置键值对个数 - 对于
chan
来说,用于设置缓存区大小,为0
或者缺省表示没有缓冲区
- 对于
- 第三个参数只有
slice
类型才有,表示实际容量大小,每一次开辟的新的空间的刻度 - 返回值类型为
T
func make(t Type, size ...IntegerType) Type // slice func make(t Type, len int [, cap int]) Type // map func make(t Type [, cap int]) Type // chan func make(t Type [, cap int]) Type
func Test_C_6(t *testing.T) { a := make([]int, 4, 8) b := make(map[int]int) c := make(chan int, 4) fmt.Printf("%v,%T\n", a, a) fmt.Printf("%v,%T\n", b, b) fmt.Printf("%v,%T\n", c, c) }
[0 0 0 0],[]int map[],map[int]int 0xc000024200,chan int
- 第一个参数接受一个类型
-
区别
- 都是用于内存分配,但是
new
所有数据类型都可以使用,而make
只能slice
、map
、chan
三种类型使用; - 返回值类型不同,
new
返回的是对应类型的指针,而make
是对应的数据类型
- 都是用于内存分配,但是
-
-
Map
-
定义
一种无序的
key:value
格式的数据结构,因为是引用类型,所以必须初始化数据格式:
map[keyType]ValueType
-
初始化
-
使用
make
初始化func Test_C_7(t *testing.T) { user := make(map[string]string, 4) user["name"] = "xxcheng" user["sex"] = "男" fmt.Printf("%v,%T,%d", user, user, len(user)) }
map[name:xxcheng sex:男],map[string]string,2
-
直接初始化
func Test_C_8(t *testing.T) { user := map[string]string{ "name": "xxcheng", "sex": "男", } fmt.Printf("%v,%T,%d", user, user, len(user)) }
map[name:xxcheng sex:男],map[string]string,2
-
-
直接取值
格式:
value,ok=map[key]
第一个返回值为取到的值,第二个返回值类型为
bool
,表示所取的key
是否存在func Test_C_9(t *testing.T) { user := map[string]string{ "name": "xxcheng", "sex": "男", } value, isExist := user["name"] value2, isExist2 := user["age"] fmt.Println(value, isExist) fmt.Println(value2, isExist2) }
xxcheng true true false
-
遍历取值
第一个返回值为取到的值,第二个返回值为键
func Test_C_10(t *testing.T) { user := map[string]string{ "name": "xxcheng", "sex": "男", "book": "海底两万里", "music": "如愿", } for value, key := range user { fmt.Println(key, value) } }
海底两万里 book 如愿 music xxcheng name 男 sex
可以看到遍历顺序和赋值顺序是不一样的,如果重新执行一遍,顺序再次不一样,这是
Go
故意这么设计的,随机从一个位置开始遍历 -
删除键值对
使用
delete
内置方法func Test_C_11(t *testing.T) { user := map[string]string{ "name": "xxcheng", "sex": "男", "book": "海底两万里", "music": "如愿", } for value, key := range user { fmt.Println(key, value) } fmt.Println("-------") delete(user, "book") delete(user, "music") for value, key := range user { fmt.Println(key, value) } }
如愿 music xxcheng name 男 sex 海底两万里 book ------- xxcheng name 男 sex
-
-
结构体
在
Go
语言中没有类的概念,没有提供class
这样的关键字来创建,但是我们可以通过结构体配合接口来满足类的需求,同时实现继承
、封装
、多态
三大特性。-
前置知识
-
自定义类型
使用
type
关键字自定义类型type MyInt int
-
类型别名
Go1.9
版本新增的功能,它只是一个别名,数据类型还是原来的数据类型type TypeAlias = Type
之前见过的两种字符类型就是类型别名
type byte = uint8 type rune = int32
-
区别
感觉使用上面没有什么区别,那他们之间有什么差异吗?
自定义类型它是一种新的数据类型,拥有原来的数据类型的特性,而类型别名只是一个别名,数据类型还是原来的数据类型
func Test_C_12(t *testing.T) { type myInt int type intAlias = int var a myInt = 1 var b intAlias = 2 fmt.Printf("%v,%T\n", a, a) fmt.Printf("%v,%T\n", b, b) }
1,main.myInt 2,int
-
-
格式
相同类型的字段可以写在一行
字段名首字母大写表示可公开访问,包外可访问,小写表示私有,仅包内可访问。
type 类型名 struct{ 字段名 字段类型 `标签` }
-
实例化
有两种实例化方式,一种是键值对的方式,一种是值列表的方式
func Test_C_13(t *testing.T) { p := Person{ "xxcheng", "男", 18, } p2 := Person{ name: "jpc", sex: "男", age: 18, } p2.name = "www" fmt.Println(p, p2) }
{xxcheng 男 18} {www 男 18}
-
匿名结构体
在一些临时的场景只实例化一次时使用
func Test_C_14(t *testing.T) { var user struct { name string sex string age int } user.name = "xxcheng" user.sex = "男" user.age = 18 fmt.Printf("%v,%T", user, user) }
{xxcheng 男 18},struct { name string; sex string; age int }
-
结构体指针
func Test_C_15(t *testing.T) { var user struct { name string sex string age int } user2 := &user user2.name = "xxcheng" user2.sex = "男" (*user2).age = 18 fmt.Printf("%#v,%T", user2, user2) }
&struct { name string; sex string; age int }{name:"xxcheng", sex:"男", age:18},*struct { name string; sex string; age int }
通过示例可以发现,使用
user2.sex = "男"
和 使用(*user2).sex = "男"
效果是一样的,这是Go
语言帮我们实现的一种语法糖。 -
结构体内存布局
这一块知识点浅学了一下,感觉知识点很有难度,设计到底层计组相关知识,先暂缓放着,否则很浪费时间
func Test_C_16(t *testing.T) { type User struct { age int8 age2 int16 age3 int32 } user := User{} fmt.Printf("user :%p,%T,%d,%d\n", &user, user, unsafe.Sizeof(user), unsafe.Alignof(user)) fmt.Printf("user.age :%p,%T,%d,%d\n", &user.age, user.age, unsafe.Sizeof(user.age), unsafe.Alignof(user.age)) fmt.Printf("user.age2:%p,%T,%d,%d\n", &user.age2, user.age2, unsafe.Sizeof(user.age2), unsafe.Alignof(user.age2)) fmt.Printf("user.age3:%p,%T,%d,%d\n", &user.age3, user.age3, unsafe.Sizeof(user.age3), unsafe.Alignof(user.age3)) }
user :0xc0000181a8,main.User,8,4 user.age :0xc0000181a8,int8,1,1 user.age2:0xc0000181aa,int16,2,2 user.age3:0xc0000181ac,int32,4,4
-
自定义实现构造函数
Go
语言没有类的概念,所以也没有构造函数的概念,但是我们可以通过函数内使用结构体创建一个,并且以返回值的形式返回,同时,因为结构体是值类型,所以return
返回时会重新创建一个新的结构体,重复开销,所以返回值使用对应类型的指针类型type Student struct { name string sex string age int } func newStudent(name, sex string, age int) *Student { return &Student{name, sex, age} } func Test_C_17(t *testing.T) { s := newStudent("xxcheng", "男", 18) fmt.Printf("%#v,%T\n", s, s) fmt.Println("name:", s.name, ",sex:", s.sex, ",age:", s.age) }
&main.Student{name:"xxcheng", sex:"男", age:18},*main.Student name: xxcheng ,sex: 男 ,age: 18
-
方法(接收者)
一种特殊类型的函数,相当于其他语言的
this
和self
官方建议接收者变量为接收者类型第一个小写字母
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) { 函数体 }
接收者类型有两种,一种是指针类型的接收者,一种是值类型的接收者
一般情况下,都最好使用指针类型的接收者,当需要对接收者的值进行修改时,只能使用指针类型的接收者
-
值类型
type Student struct { name string sex string age int } func (s Student) SayHello() { fmt.Println("你好,我叫", s.name, ",性别是", s.sex, ",年龄是", s.age) } func Test_C_18(t *testing.T) { s := Student{"xxcheng", "男", 18} s.SayHello() }
你好,我叫 xxcheng ,性别是 男 ,年龄是 18
-
指针类型
type Student struct { name string sex string age int } func (s Student) SayHello() { fmt.Println("你好,我叫", s.name, ",性别是", s.sex, ",年龄是", s.age) } func (s *Student) UpdateName(name string) { s.name = name } func Test_C_19(t *testing.T) { s := Student{"xxcheng", "男", 18} s.UpdateName("jpc") s.SayHello() }
你好,我叫 jpc ,性别是 男 ,年龄是 18
同时,方法(接收者)并不是只能给结构体使用,可以给任何数据类型都可以使用
type myInt int func (m myInt) SayCurrentInt() { fmt.Println("当前的值为:", m) } func Test_C_20(t *testing.T) { var a myInt = 1 a.SayCurrentInt() }
当前的值为: 1
上面示例我们可以给我们自定义类型
myInt
定义一个方法,但是不能直接给int
定义一个方法,因为方法(接收者)只能在本地定义,也就是只能在当前包定义 -
-
匿名字段
结构体中还有一种特殊的字段,
匿名字段
只写数据类型,而不写变量type Human struct { string int sex string } func Test_C_21(t *testing.T) { h := Human{"xxcheng", 18, "男"} fmt.Printf("%#v,%T\n", h, h) fmt.Println(h.string) fmt.Println(h.int) fmt.Println(h.sex) }
main.Human{string:"xxcheng", int:18, sex:"男"},main.Human xxcheng 18 男
和普通的字段没什么差别,只不过是字段名称是对应的
数据类型
的名称 -
嵌套结构体
type Book struct { name string price float32 } type TextBook struct { subject string Book Book } func Test_C_22(t *testing.T) { a := TextBook{"数学", Book{"数学书", 10.99}} fmt.Println(a) }
{数学 {数学书 10.99}}
-
匿名嵌套结构体
匿名嵌套结构体在自身的字段找不到字段的情况下,可以直接访问被嵌套的结构体的字段
type A struct { name string age int } type B struct { name string A } func Test_C_23(t *testing.T) { b := B{"xxcheng", A{"jpc", 18}} b.name = "www" b.age = 999 fmt.Printf("%#v\n", b) }
main.B{name:"www", A:main.A{name:"jpc", age:999}}
-
匿名嵌套结构体嵌套的嵌套
当前字段名找不到,就会顺着匿名结构体一层一层往下找
type F struct { name string age int } type G struct { name2 string F } type H struct { name3 string G } func Test_C_25(t *testing.T) { a := H{"hhh", G{"ggg", F{"fff", 18}}} a.name = "www" a.age = 999 fmt.Printf("%#v\n", a) }
main.H{name3:"hhh", G:main.G{name2:"ggg", F:main.F{name:"www", age:999}}}
-
匿名嵌套结构体发生冲突
当两个匿名嵌套结构体字段相同时,就无法直接访问了,会造成歧义,就需要指定字段名访问了
type C struct { name string age int } type D struct { name string age int } type E struct { name string C D } func Test_C_24(t *testing.T) { b := E{"xxcheng", C{"ccc", 11}, D{"ddd", 22}} b.name = "www" b.age = 999 fmt.Printf("%#v\n", b) }
# command-line-arguments [command-line-arguments.test] ./c_test.go:292:4: ambiguous selector b.age FAIL command-line-arguments [build failed] FAIL
修改为
func Test_C_24(t *testing.T) { b := E{"xxcheng", C{"ccc", 11}, D{"ddd", 22}} b.name = "www" b.C.age = 999 fmt.Printf("%#v\n", b) }
main.E{name:"www", C:main.C{name:"ccc", age:999}, D:main.D{name:"ddd", age:22}}
-
-
结构体的
继承
基于匿名结构体会自动寻找对应的字段名这一特点,可以实现其
继承
的特点type Animal struct { name string } type Cat struct { speed int Animal } func (c Cat) run() { fmt.Println(c.name, "正在奔跑,速度为:", c.speed) } type Dog struct { color string Animal } func (d Dog) dark() { fmt.Println(d.name, "正在叫,它的颜色是:", d.color) } func Test_C_26(t *testing.T) { a := Cat{20, Animal{"咪咪"}} b := Dog{"白色", Animal{"小白"}} a.run() b.dark() }
咪咪 正在奔跑,速度为: 20 小白 正在叫,它的颜色是: 白色
-
结构体的序列化与反序列化
摘自百度百科
JSON(JavaScript Object Notation, JS对象简谱)是一种轻量级的数据交换格式。它基于 ECMAScript(European Computer Manufacturers Association, 欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
Go
语言可以使用encoding/json
这个库提供的Marshal
函数序列化,Unmarshal
函数反序列化。func Marshal(v any) ([]byte, error)
func Unmarshal(data []byte, v any) error
func Test_C_27(t *testing.T) { m := Man{"Tom", 18} jsonByte, ok := json.Marshal(m) var str string = string(jsonByte) fmt.Println(str, ok) var myJsonStr string = "{\"Name\":\"xxcheng\",\"Age\":22}" var m2 Man err := json.Unmarshal([]byte(myJsonStr), &m2) fmt.Printf("%v,%#v", err, m2) }
{"Name":"Tom","Age":18} <nil> <nil>,main.Man{Name:"xxcheng", Age:22}
-
结构体标签
Tag
是结构体的元信息,可以在运行的时候通过反射的机制读取出来。在结构体字段的后面使用反引号(
`
)括起来,多对使用空格分割`key1:"value1" key2:"value2"`
-
标签与序列化相结合
当我们把一个结构体确认之后,如果想修改对应生成的
json
的字段,这是在生成环境来说是不可能的,使用我们可以利用标签来自定义json
的字段,我们添加一个字段名为json
的键值对即可type Woman struct { Name string `json:"name" desc:"姓名"` Age int `json:"aaa"` } func (w Woman) Walk() { fmt.Println("女人正在散步...") } func Test_C_28(t *testing.T) { w := Woman{"Jerry", 18} jsonByte, _ := json.Marshal(w) fmt.Printf("%s\n", jsonByte) }
{"name":"Jerry","aaa":18}
-
-
-
参考链接
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。