Golang M 2023 6 topic

Posted 知其黑、受其白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang M 2023 6 topic相关的知识,希望对你有一定的参考价值。

阅读目录

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的主要内容,如果未能解决你的问题,请参考以下文章

Golang M 2023 5 * theory

Golang M 2023 5 * theory

Golang M 2023 4 topic

面向校招全力备战2023Golang实习与校招

面向校招全力备战2023Golang实习与校招

面向校招全力备战2023Golang实习与校招