Golang M 2023 6 topic
Posted 知其黑、受其白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang M 2023 6 topic相关的知识,希望对你有一定的参考价值。
阅读目录
- topic
- defer panic
- slice make
- new() 与 make()
- 变量声明的简短模式
- 结构体
- 题
- 1 下面这段代码能否通过编译?如果通过,输出什么?
- 2 关于字符串连接,下面语法正确的是?
- 3 下面这段代码能否编译通过?如果可以,输出什么?
- 4 下面赋值正确的是?
- 5 关于 init 函数,下面说法正确的是?
- 6 下面这段代码输出什么以及原因?
- 7 下面这段代码能否编译通过?如果可以,输出什么?
- 8 关于channel,下面语法正确的是?
- 9 下面这段代码输出什么?
- 10 下面这段代码输出什么?
- 11 关于 cap() 函数的适用类型,下面说法正确的是?
- 12 下面这段代码输出什么?
- 13 下面这段代码输出什么?
- 14 下面属于关键字的是?
- 15 下面这段代码输出什么?
- 16 下面这段代码输出什么?
- 17 定义一个包内全局字符串变量,下面语法正确的是?
- 18 下面这段代码输出什么?
- 19 下面代码输出什么?
- 20 下面代码输出什么?
- 21 下面代码下划线处可以填入哪个选项?
- 22 下面这段代码输出什么?
- 23 下面这段代码输出什么?
- 24 切片 a、b、c 的长度和容量分别是多少?
- 25 下面代码中 A B 两处应该怎么修改才能顺利编译?
- 26 下面代码中,x 已声明,y 没有声明,判断每条语句的对错?
- 27 下面代码输出什么?
- 28 类型断言
- 29 匿名返回
topic
类型转换和类型断言
类型转换语法:Type(expression)
类型断言语法为:expression.(Type)
1 类型转换示例代码
package main
import "fmt"
//典型的类型转换示例
func main()
// 类型转换示例
var a int = 5
var b int = 2
var c float32
c = float32(a) / float32(b) //这里就是典型的类型转换
fmt.Printf("a的类型:%T\\n", a)
fmt.Printf("b的类型:%T\\n", b)
fmt.Printf("c的类型:%T\\n", c)
fmt.Printf("c的值:%v\\n", c)
//如果我们不进行类型转换,看下输出结果
fmt.Printf("不进行类型转换的输出结果:%v\\n", a/b) //从结果可以看出值为2,丢失了精度
PS E:\\TEXT\\test_go\\one> go run .\\main.go
a的类型:int
b的类型:int
c的类型:float32
c的值:2.5
不进行类型转换的输出结果:2
PS E:\\TEXT\\test_go\\one>
2 类型断言代码示例
注意:对于类型断言,expression 必须是接口类型。
package main
import "fmt"
//经典的类型断言示例 搭配switch使用
func main()
var x interface
x = 1
//这就是上面说的expression.(Type)
switch x.(type)
case float32:
fmt.Println("类型是:float32")
case string:
fmt.Println("类型是:string")
case int:
fmt.Println("类型是:int")
default:
fmt.Println("未知类型")
PS E:\\TEXT\\test_go\\one> go run .\\main.go
类型是:int
PS E:\\TEXT\\test_go\\one>
全局变量
定义全局变量必须用 var
,全局变量要定义在函数之外,而在函数之外定义的变量只能用 var
定义。
全局变量使用 var,编译通过。
package main
var name = "test"
func main()
// fmt.Println(name)test
全局变量不使用 var,编译不通过。
Go语言中变量、init函数、main函数的执行顺序
1 首先初始化导入包的变量和常量。
2 然后执行导入包的 init 函数。
3 然后初始化本包的变量和常量。
4 然后执行本包的 init 函数。
5 最后执行本包的 main 函数。
Go接口总结
如果两个接口拥有相同方法列表(顺序可以不一致),那么这两个接口实质上同一个接口。
接口 A 是接口 B 的子集,意味着 A 的方法 B 中都有,那么 A 是 B 的基类,所以A=B 是可行的。
接口是否能够调用成功,需要运行的时候才能知道。
接口赋值是否可行,在编译阶段就可以知道。
Go字符串
Go语言中的字符串不支持下标操作:
在 go 语言中,字符串是一种基本类型,和其它开发语言不同,Go 的字符串是由单个字节连接起来的。
Go 语言统一使用 utf-8 编码标识 Unicode 文本。当字符为 ASCII 编码时,占用 1个字节,其它字符占用 2 到 4 个字节,中文占用 3 个字节。
如何修改字符串的内容有三种方法:
1 将字符串转成 byte 切片,再根据下标替换内容。
2 将字符串转为 rune 切片,再根据下标替换内容。
3 使用原生包 strings 中的 Replace() 方法。
package main
import (
"fmt"
"strings"
)
func main()
//第一种方法 将字符串转成byte切片
s := "hello"
s2 := []byte(s)
s2[0] = 'x'
fmt.Printf("转成byte切片:%v\\n", string(s2))
//打印结果:转成byte切片:xello
// 第二种方法 将字符串转为rune切片
s3 := "潜力股"
//注意:中文字符串要进行修改,只能转成rune切片,不能转成byte切片
s4 := []rune(s3)
s4[1] = '水'
fmt.Printf("转成rune切片:%v\\n", string(s4))
//打印结果:转成rune切片:潜水股
// 第三种方法,使用原生包strings 中的 Replace() 方法
s5 := "abcdef"
old := "abc"
newString := "ABC"
//最后一个参数n的作用是:返回将s5中前n个不重叠old子串都替换为new的新字符串,如果n<0会替换所有old子串。
s6 := strings.Replace(s5, old, newString, -1)
fmt.Printf("strings替换之后的:%v\\n", s6)
//打印结果:strings替换之后的:ABCdef
PS E:\\TEXT\\test_go\\one> go run .\\main.go
转成byte切片:xello
转成rune切片:潜水股
strings替换之后的:ABCdef
PS E:\\TEXT\\test_go\\one>
slice
slice 可以通过 append 方式实现元素的删除。
*
切片使用冒号分隔时遵循"前闭后开"
原则,即包括前面的值,不包括后面的值。
1 删除单个元素
package main
import "fmt"
//使用append 删除单个元素
func main()
var data = []int0, 1, 2, 3, 4, 5
//删除元素2
index := 2
//切片使用冒号分隔时遵循"前闭后开"原则,即包括前面的值,不包括后面的值。
data1 := append(data[:index], data[index+1:]...)
fmt.Println(data1)
PS E:\\TEXT\\test_go\\one> go run .\\main.go
[0 1 3 4 5]
PS E:\\TEXT\\test_go\\one>
2 删除多个元素
package main
import "fmt"
//使用append 删除多个元素
func main()
var data = []int0, 1, 2, 3, 4, 5
//删除元素0,1,2
index := 2
//切片使用冒号分隔时遵循"前闭后开"原则,即包括前面的值,不包括后面的值。
data1 := append(data[:0], data[index+1:]...)
fmt.Println(data1)
PS E:\\TEXT\\test_go\\one> go run .\\main.go
[3 4 5]
PS E:\\TEXT\\test_go\\one>
3 切片下标打印显示
package main
import (
"fmt"
"sort"
)
func main()
// var x = []int2: 2, 3, 0: 1
// fmt.Println(x) // 用了下标显示 [1 0 2 3]
var x = []int2, 1, 0, 3
fmt.Println(x) // [2 1 0 3]
sort.Ints(x)
fmt.Println(x) // [0 1 2 3]
Go 语言是如何实现切片扩容的?
package main
import "fmt"
func main()
arr := make([]int, 0)
for i := 0; i < 200; i++
fmt.Println("len 为", len(arr), "cap 为", cap(arr))
arr = append(arr, i)
看下面代码的 defer 的执行顺序是什么? defer的作用和特点是什么?
defer的作用是:
你只需要在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。
当defer语句被执行时,跟在defer后面的函数会被延迟执行。
直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。
你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。
defer 的常用场景:
通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。
释放资源的defer应该直接跟在请求资源的语句后。
Golang Slice 的底层实现
切片是基于数组实现的,它的底层是数组,它自己本身非常小,可以理解为对底层数组的抽象。
因为基于数组实现,所以它的底层的内存是连续分配的,效率非常高,还可以通过索引获得数据,可以迭代以及垃圾回收优化。
切片本身并不是动态数组或者数组指针。
它内部实现的数据结构通过指针引用底层数组,设定相关属性将数据读写操作限定在指定的区域内。
切片本身是一个只读对象,其工作机制类似数组指针的一种封装。
切片对象非常小,是因为它是只有3个字段的数据结构:
- 指向底层数组的指针
- 切片的长度
- 切片的容量
Golang Slice 的扩容机制,有什么注意点?
Go 中切片扩容的策略是这样的:
首先判断,如果新申请容量大于2倍的旧容量,最终容量就是新申请的容量。
否则判断,如果旧切片的长度小于1024,则最终容量就是旧容量的两倍。
否则判断,如果旧切片长度大于等于1024,则最终容量从旧容量开始循环增加原来的 1/4, 直到最终容量大于等于新申请的容量。
如果最终容量计算值溢出,则最终容量就是新申请容量。
扩容前后的 Slice 是否相同?
情况一:
原数组还有容量可以扩容(实际容量没有填充完),这种情况下,扩容以后的数组还是指向原来的数组,对一个切片的操作可能影响多个指针指向相同地址的Slice。
情况二:
原来数组的容量已经达到了最大值,再想扩容, Go 默认会先开一片内存区域,把原来的值拷贝过来,然后再执行 append() 操作。
这种情况丝毫不影响原数组。
要复制一个Slice,最好使用Copy函数。
切片赋值
1 切片赋值后修改赋值的变量原数据会变
package main
import "fmt"
func main()
// 设置元素数量为1000
const elementCount = 3
// 预分配足够多的元素切片
srcData := make([]int, elementCount)
// 将切片赋值
for i := 0; i < elementCount; i++
srcData[i] = i
// 引用切片数据
refData := srcData
srcData[0] = 999
refData[1] = 888
fmt.Println(refData)
fmt.Println(srcData)
E:\\TEXT\\test_go\\one\\test>go run main.go
[999 888 2]
[999 888 2]
E:\\TEXT\\test_go\\one\\test>
2 slice复制,需要使用copy(dst, src)函数
这是copy的一个坑,如果要做slice复制,需要使用copy(dst, src)函数。
但是copy实际复制的元素个数是从两个slice中取最小值,即min(len(dst), len(src)),如果len(dst)=0则没有办法完成复制。
func main()
src := []int1, 2, 3
dst := make([]int, 0)
copy(dst, src)
fmt.Print(dst) // []
func main()
src := []int1, 2, 3
dst := make([]int, len(src))
copy(dst, src)
fmt.Print(dst) //[1 2 3]
3 copy 复制到另一个数组切片中,修改值两个变量不会相互影响
package main
import "fmt"
func main()
src := []int1, 2, 3
dst := make([]int, len(src))
copy(dst, src)
// fmt.Print(dst) //[1 2 3]
dst[1] = 8888
fmt.Println(src)
fmt.Println(dst)
defer panic
参考解析:
defer 的执行顺序是后进先出。
当出现 panic 语句的时候,会先按照 defer 的后进先出的顺序执行,最后才会执行panic。
package main
import "fmt"
func main()
defer_all()
func defer_all()
defer func() fmt.Println("打印前") ()
defer func() fmt.Println("打印中") ()
defer func() fmt.Println("打印后") ()
panic("触发异常")
E:\\TEXT\\test_go\\one\\test>go run main.go
打印后
打印中
打印前
panic: 触发异常
goroutine 1 [running]:
main.defer_all()
E:/TEXT/test_go/one/test/main.go:13 +0x6b
main.main()
E:/TEXT/test_go/one/test/main.go:6 +0x17
exit status 2
E:\\TEXT\\test_go\\one\\test>
slice make
1 遍历
package main
import "fmt"
func main()
slice := []int0, 1, 2, 3
m := make(map[int]*int)
for key, val := range slice
m[key] = &val
for k, v := range m
fmt.Println(k, "->", *v)
直接给答案:
E:\\TEXT\\test_go\\one\\test>go run main.go
1 -> 3
2 -> 3
3 -> 3
0 -> 3
E:\\TEXT\\test_go\\one\\test>
参考解析:
这是新手常会犯的错误写法,for range 循环的时候会创建每个元素的副本,而不是元素的引用,所以 m[key] = &val
取的都是变量 val 的地址,所以最后 map 中的所有元素的值都是变量 val 的地址,因为最后 val 被赋值为3,所有输出都是3。
正确的写法:
package main
import "fmt"
func main()
slice := []int0, 1, 2, 3
m := make(map[int]*int)
for key, val := range slice
value := val
m[key] = &value
for k, v := range m
fmt.Println(k, "->", *v)
E:\\TEXT\\test_go\\one\\test>go run main.go
3 -> 3
0 -> 0
1 -> 1
2 -> 2
E:\\TEXT\\test_go\\one\\test>
2 下面这段代码能否通过编译,如果可以,输出什么?
package main
import "fmt"
func main()
s1 := []int1, 2, 3
s2 := []int5, 6
s1 = append(s1, s2)
fmt.Println(s1)
E:\\TEXT\\test_go\\one\\test>go run main.go
# command-line-arguments
.\\main.go:8:18: cannot use s2 (variable of type []int) as type int in argument to append
E:\\TEXT\\test_go\\one\\test>
参考答案及解析:
不能通过编译。
append() 的第二个参数不能直接使用 slice,需使用 …
操作符,将一个切片追加到另一个切片上:append(s1,s2…)
。
或者直接跟上元素,形如:append(s1,1,2,3)
。
package main
import "fmt"
func main()
s1 := []int1, 2, 3
s2 := []int5, 6
s1 = append(s1, s2...)
fmt.Println(s1)
// [1 2 3 5 6]
new() 与 make()
1 下面两段代码输出什么?
package main
import "fmt"
func main()
s := make([]int, 5)
s = append(s, 1, 2, 3)
fmt.Println(s)
// [0 0 0 0 0 1 2 3]
package main
import "fmt"
func main()
s := make([]int, 0)
s = append(s, 1, 2, 3, 4)
fmt.Println(s)
// [1 2 3 4]
参考解析:
这道题考的是使用 append 向 slice 添加元素,第一段代码常见的错误是 [1 2 3],需要注意。
2 下面这段代码有什么缺陷
func funcMui(x,y int)(sum int,error)
return x+y,nil
参考答案:第二个返回值没有命名。
参考解析:
在函数有多个返回值时,只要有一个返回值有命名,其他的也必须命名。如果有多个返回值必须加上括号();
如果只有一个返回值且命名也必须加上括号()。
这里的第一个返回值有命名 sum,第二个没有命名,所以错误。
3 new() 与 make() 的区别
参考答案:
new(T) 和 make(T,args) 是 Go 语言内建函数,用来分配内存,但适用的类型不同。
new(T) 会为 T 类型的新值分配已置零的内存空间,并返回地址(指针),即类型为 *T
的值。换句话说就是,返回一个指针,该指针指向新分配的、类型为 T 的零值。
适用于值类型,如数组、结构体等。
make(T,args) 返回初始化之后的 T 类型的值,这个值并不是 T 类型的零值,也不是指针 *T
,是经过初始化之后的 T 的引用。
make() 只适用于 slice、map 和 channel。
4 下面这段代码能否通过编译,不能的话原因是什么;如果能,输出什么?
package main
import "fmt"
func main()
list := new([]int)
list = append(list, 1)
fmt.Println(list)
参考答案及解析:
不能通过编译,new([]int)
之后的 list 是一个 *[]int
类型的指针,不能对指针执行 append 操作。
可以使用 make() 初始化之后再用。
同样的,map 和 channel 建议使用 make() 或字面量的方式初始化,不要用 new() 。
package main
import "fmt"
func main()
list := make([]int, 0)
list = append(list, 1)
fmt.Println(list)
5 通过指针变量 p 访问其成员变量 name,有哪几种方式?
A.p.name
B.(&p).name
C.(*p).name
D.p->name
参考答案及解析:AC
。&
取址运算符,*
指针解引用。
变量声明的简短模式
1 下面这段代码能否通过编译,如果可以,输出什么?
package main
import "fmt"
var (
size:=1024
max_size = size * 2
)
func main()
fmt.Println(size, max_size)
参考答案及解析:
不能通过编译。
这道题的主要知识点是变量声明的简短模式,形如:x := 100
。
但这种声明方式有限制:
1.必须使用显示初始化;
2.不能提供数据类型,编译器会自动推导;
3.只能在函数内部使用简短模式;
结构体
1 下面这段代码能否通过编译?不能的话,原因是什么?如果通过,输出什么?
package main
import "fmt"
func main()
sn1 := struct
age int
name string
age: 11, name: <以上是关于Golang M 2023 6 topic的主要内容,如果未能解决你的问题,请参考以下文章