Golang basic_leamingM 2023 1
Posted 知其黑、受其白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang basic_leamingM 2023 1相关的知识,希望对你有一定的参考价值。
阅读目录
数据定义
函数返回值问题
1 下面代码是否可以编译通过?
package main
/*
下面代码是否编译通过?
*/
func myFunc(x,y int)(sum int,error)
return x+y,nil
func main()
num, err := myFunc(1, 2)
fmt.Println("num = ", num)
答案:编译报错理由
# command-line-arguments
./test1.go:6:21: syntax error: mixed named and unnamed function parameters
考点:函数返回值命名
结果:编译出错。
在函数有多个返回值时,只要有一个返回值有指定命名,其他的也必须有命名。
如果返回值有多个返回值必须加上括号;
如果只有一个返回值并且有命名也需要加上括号;
此处函数第一个返回值有 sum
名称,第二个未命名,所以错误。
可以这么写:
package main
import "fmt"
/*
下面代码是否编译通过?
*/
func myFunc(x, y int) (sum int, err error)
return x + y, nil
func main()
num, err := myFunc(1, 2)
if err != nil
fmt.Println("错误:", err)
fmt.Println("num = ", num)
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
num = 3
PS E:\\TEXT\\test_go\\one>
切片
1 切片是引用数据类型 – 注意切片的赋值拷贝
下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。
package main
import "fmt"
func myFunc()
// a := [8]int0, 1, 2, 3, 4, 5, 6, 7
// s1 := a[:5] // [0 1 2 3 4]
// s2 := a[3:6] // [3 4 5]
func Zi()
s1 := make([]int, 3)
//[0 0 0]
s2 := s1
//将s1 直接赋值给s2,s1 和s2 共用一个底层数组
s2[0] = 100
fmt.Println(s1)
//[100 0 0]
fmt.Println(s2)
//[100 0 0]
func main()
myFunc()
Zi()
结构体比较问题
1 下面代码是否可以编译通过?
为什么?
package main
import "fmt"
func main()
sn1 := struct
age int
name string
age: 11, name: "qq"
sn2 := struct
age int
name string
age: 11, name: "qq"
if sn1 == sn2
fmt.Println("sn1 == sn2")
sm1 := struct
age int
m map[string]string
age: 11, m: map[string]string"a": "1"
sm2 := struct
age int
m map[string]string
age: 11, m: map[string]string"a": "1"
if sm1 == sm2
fmt.Println("sm1 == sm2")
结果:编译不通过
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
# command-line-arguments
.\\test1.go:31:5: invalid operation: sm1 == sm2 (struct containing map[string]string cannot be compared)
PS E:\\TEXT\\test_go\\one>
考点:结构体比较
结构体比较规则注意1:
只有相同类型的结构体才可以比较,结构体是否相同不但与属性类型个数有关,还与属性顺序相关。
比如:顺序不一致报错
sn1 := struct
age int
name string
age: 11, name: "qq"
sn3:= struct
name string
age int
age:11, name:"qq"
sn3
与 sn1
就不是相同的结构体了,不能比较。
结构体比较规则注意2:
结构体是相同的,但是结构体属性中有不可以比较的类型,如 map,slice,则结构体不能用 ==
比较。
可以使用 reflect.DeepEqual
进行比较:
package main
import (
"fmt"
"reflect"
)
func main()
sm1 := struct
age int
m map[string]string
age: 11, m: map[string]string"a": "1"
sm2 := struct
age int
m map[string]string
age: 11, m: map[string]string"a": "1"
if reflect.DeepEqual(sm1, sm2)
fmt.Println("sm1 == sm2")
else
fmt.Println("sm1 != sm2")
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
sm1 == sm2
PS E:\\TEXT\\test_go\\one>
string 与 nil 类型
1 下面代码是否能够编译通过?
为什么?
考点:函数返回值类型
答案:编译不会通过。
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
# command-line-arguments
.\\test1.go:11:9: cannot use nil as string value in return statement
PS E:\\TEXT\\test_go\\one>
分析:
nil
可以用作 interface、function、pointer、map、slice 和 channel 的 “空值”
。
但是如果不特别指定的话,Go 语言不能识别类型,所以会报错。通常编译的时候不会报错,但是运行是时候会报: cannot use nil as type string in return argument
。
常量
1 下面函数有什么问题?
解析考点:常量
常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用。
内存四区概念:
A 数据类型本质:固定内存大小的别名。
B 数据类型的作用:编译器预算对象(变量)分配的内存空间大小。
流程说明
1、操作系统把物理硬盘代码 load 到内存。
2、操作系统把 c 代码分成四个区。
3、操作系统找到 main 函数入口执行。
栈区(Stack):
空间较小,要求数据读写性能高,数据存放时间较短暂。
由编译器自动分配和释放,存放函数的参数值、函数的调用流程方法地址、局部变量等(局部变量如果产生逃逸现象,可能会挂在在堆区)
堆区(heap):
空间充裕,数据存放时间较久。
一般由开发者分配及释放(但是Golang中会根据变量的逃逸现象来选择是否分配到栈上或堆上),启动Golang的GC由GC清除机制自动回收。
全局区-静态全局变量区
全局变量的开辟是在程序在main之前就已经放在内存中。
而且对外完全可见。
即作用域在全部代码中,任何同包代码均可随时使用,在变量会搞混淆,而且在局部函数中如果同名称变量使用 :=
赋值会出现编译错误。
全局变量最终在进程退出时,由操作系统回收。我们在开发的时候,尽量减少使用全局变量的设计。
全局区-常量区:
常量区也归属于全局区,常量为存放数值字面值单位,即不可修改。或者说的有的常量是直接挂钩字面值的。
比如:
const cl = 10
cl 是字面量 10 的对等符号。
所以在golang中,常量是无法取出地址的,因为字面量符号并没有地址而言。
数组和切片
切片的初始化与追加
package main
import (
"fmt"
)
func main()
s := make([]int, 10)
s = append(s, 1, 2, 3)
fmt.Println(s)
// [0 0 0 0 0 0 0 0 0 0 1 2 3]
考点:切片追加, make 初始化均为 0。
slice 拼接问题
编译失败
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
# command-line-arguments
.\\test1.go:8:18: cannot use s2 (variable of type []int) as type int in argument to append
PS E:\\TEXT\\test_go\\one>
两个 slice 在 append 的时候,记住需要进行将第二个 slice 进行 ...
打散再拼接。
package main
import "fmt"
func main()
s1 := []int1, 2, 3
s2 := []int4, 5
s1 = append(s1, s2...)
fmt.Println(s1)
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
[1 2 3 4 5]
PS E:\\TEXT\\test_go\\one>
slice 中 new 的使用
package main
import "fmt"
func main()
list := new([]int)
list = append(list, 1)
fmt.Println(list)
编译失败:
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
# command-line-arguments
.\\test1.go:9:16: first argument to append must be a slice; have list (variable of type *[]int)
PS E:\\TEXT\\test_go\\one>
分析:
- 切片指针的解引用。
- 可以使用
list:=make([]int,0) list
类型为切片。 - 或使用
*list = append(*list, 1) list
类型为指针。
new 和 make 的区别
二者都是内存的分配(堆上)。
make 只用于 slice、map 以及 channel 的初始化(非零值);
而 new 用于类型的内存分配,并且内存置为零。
所以在我们编写程序的时候,就可以根据自己的需要很好的选择了。
make 返回的还是这三个引用类型本身;
而 new 返回的是指向类型的指针。
Map
Map 的 Value 赋值
编译失败,
./test7.go:18:23: cannot assign to struct field list["student"].Name in map
分析
map[string]Student
的 value 是一个 Student 结构值,所以当list["student"] = student
,是一个值拷贝过程。
而 list["student"]
则是一个值引用。
那么值引用的特点是只读。
所以对 list["student"].Name = "LDB"
的修改是不允许的。
方法一
package main
import "fmt"
type Student struct
Name string
var list map[string]Student
func main()
list = make(map[string]Student)
student := Student"Aceld"
list["student"] = student
//list["student"].Name = "LDB"
/*
方法1:
*/
tmpStudent := list["student"]
tmpStudent.Name = "LDB"
list["student"] = tmpStudent
fmt.Println(list["student"]) // LDB
其中
/*
方法1:
*/
tmpStudent := list["student"]
tmpStudent.Name = "LDB"
list["student"] = tmpStudent
是先做一次值拷贝,做出一个 tmpStudent 副本,然后修改该副本,然后再次发生一次值拷贝复制回去,list["student"] = tmpStudent
,但是这种会在整体过程中发生2次结构体值拷贝,性能很差。
方法二
package main
import "fmt"
type Student struct
Name string
var list map[string]*Student
func main()
list = make(map[string]*Student)
student := Student"Aceld"
list["student"] = &student
list["student"].Name = "LDB"
fmt.Println(list["student"]) // &LDB
我们将 map 的类型的 value 由 Student 值,改成 Student 指针。
var list map[string]*Student
这样,我们实际上每次修改的都是指针所指向的 Student 空间,指针本身是常指针,不能修改,只读属性,但是指向的 Student 是可以随便修改的,而且这里并不需要值拷贝。
只是一个指针的赋值。
map 的遍历赋值
package main
import (
"fmt"
)
type student struct
Name string
Age int
func main()
//定义map
m := make(map[string]*student)
//定义student数组
stus := []student
Name: "zhou", Age: 24,
Name: "li", Age: 23,
Name: "wang", Age: 22,
//将数组依次添加到map中
for _, stu := range stus
m[stu.Name] = &stu
//打印map
for k, v := range m
fmt.Println(k, "=>", v.Name)
错误结果
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
zhou => wang
li => wang
wang => wang
PS E:\\TEXT\\test_go\\one>
map 中的 3 个 key 均指向数组中最后一个结构体。
分析
foreach 中,stu 是结构体的一个拷贝副本,所以 m[stu.Name]=&stu
实际上一致指向同一个指针, 最终该指针的值为遍历的最后一个 struct 的值拷贝。
正确写法 1
package main
import (
"fmt"
)
type student struct
Name string
Age int
func main()
//定义map
m := make(map[string]*student)
//定义student数组
stus := []student
Name: "zhou", Age: 24,
Name: "li", Age: 23,
Name: "wang", Age: 22,
// 遍历结构体数组,依次赋值给map
for i := 0; i < len(stus); i++
m[stus[i].Name] = &stus[i]
//打印map
for k, v := range m
fmt.Println(k, "=>", v.Name)
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
zhou => zhou
li => li
wang => wang
PS E:\\TEXT\\test_go\\one>
正确写法 2
package main
import "fmt"
type student struct
Name string
Age int
func main()
//定义map
m := make(map[string]*student)
//定义student数组
stus := []student
Name: "zhou", Age: 24,
Name: "li", Age: 23,
Name: "wang", Age: 22,
//将数组依次添加到map中
for key, _ := range stus
m[stus[key].Name] = &stus[key]
//打印map
for k, v := range m
fmt.Println(k, "=>", v.Name)
interface
interface 的赋值问题
继承与多态的特点,在golang中对多态的特点体现从语法上并不是很明显。
我们知道发生多态的几个要素:
1、有 interface 接口,并且有接口定义的方法。
2、有子类去重写 interface 的接口。
3、有父类指针指向子类的具体对象。
那么,满足上述3个条件,就可以产生多态效果,就是,父类指针可以调用子类的具体方法。
所以上述代码报错的地方在 var peo People = Stduent
这条语句, Student 已经重写了父类 People 中的 Speak(string) string
方法,那么只需要用父类指针指向子类对象即可。
所以应该改成 var peo People = &Student
即可编译通过。
(People为interface类型,就是指针类型)
package main
import (
"fmt"
)
type People interface
Speak(string) string
type Stduent struct
func (stu *Stduent) Speak(think string) (talk string)
if think == "love"
talk = "You are a good boy"
else
talk = "hi"
return
func main()
var peo People = &Stduent
think := "loves"
fmt.Println(peo.Speak(think))
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
hi
PS E:\\TEXT\\test_go\\one>
interface 的内部构造(非空接口 iface 情况)
分析:
我们需要了解 interface 的内部结构,才能理解这个题目的含义。
interface 在使用的过程中,共有两种表现形式:
1 一种为空接口(empty interface),定义如下:
var MyInterface interface
2 非空接口(non-empty interface), 定义如下:
type MyInterface interface
function()
这两种 interface 类型分别用两种struct表示,空接口为eface, 非空接口为iface.
空接口 eface
空接口 eface 结构,由两个属性构成,一个是类型信息 _type,一个是数据信息。
其数据结构声明如下:
type eface struct //空接口
_type *_type //类型信息
data unsafe.Pointer
//指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
_type属性:
是GO语言中所有类型的公共描述,Go语言几乎所有的数据结构都可以抽象成 _type,是所有类型的公共描述,type负责决定data应该如何解释和操作,type的结构代码如下:
type _type struct
size uintptr //类型大小
ptrdata uintptr //前缀持有所有指针的内存大小
hash uint32 //数据hash值
tflag tflag
align uint8 //对齐
fieldalign uint8 //嵌入结构体时的对齐
kind uint8 //kind 有些枚举值kind等于0是无效的
alg *typeAlg //函数指针数组,类型实现的所有方法
gcdata *byte
str nameOff
ptrToThis typeOff
data属性:
表示指向具体的实例数据的指针,他是一个 unsafe.Pointer 类型,相当于一个 C 的万能指针 void*。
非空接口 iface
iface 表示 non-empty interface 的数据结构,非空接口初始化的过程就是初始化一个iface类型的结构,其中data的作用同eface的相同,这里不再多加描述。
type iface struct
tab *itab
data unsafe.Pointer
iface结构中最重要的是 itab 结构(结构如下),每一个 itab 都占 32 字节的空间。
itab可以理解为pair<interface type, concrete type> 。
itab里面包含了interface的一些关键信息,比如method的具体实现。
type itab struct
inter *interfacetype // 接口自身的元信息
_type *_type // 具体类型的元信息
link *itab
bad int32
hash int32 // _type里也有一个同样的hash,此处多放一个是为了方便运行接口断言
fun [1]uintptr // 函数指针,指向具体类型所实现的方法
所以,People拥有一个Show方法的,属于非空接口,People的内部定义应该是一个iface结构体。
type People interface
ShowGolang basic_leamingM 2023 1
Golang basic_leamingM 2023 3 topic