zl程序教程

您现在的位置是:首页 >  工具

当前栏目

Golang M 2023 6 topic

Golang 2023 topic
2023-09-11 14:14:56 时间

阅读目录

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 = []int{0, 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 = []int{0, 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 = []int{2: 2, 3, 0: 1}
	// fmt.Println(x) // 用了下标显示 [1 0 2 3]
	var x = []int{2, 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 := []int{1, 2, 3}
    dst := make([]int, 0)
    copy(dst, src)
    fmt.Print(dst) // []
}
func main() {
    src := []int{1, 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 := []int{1, 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 := []int{0, 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 := []int{0, 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 := []int{1, 2, 3}
	s2 := []int{5, 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 := []int{1, 2, 3}
	s2 := []int{5, 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: "qq"}

	sn2 := struct {
		age  int
		name string
	}{age: 11, name: "qq"}

	if sn1 == sn2 {
		fmt.Println("sn1 == sn2")
		// 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")
	}
}

参考答案及解析:
编译不通过 invalid operation: sm1 == sm2

这道题目考的是结构体的比较,有几个需要注意的地方:

  • 1 结构体只能比较是否相等,但是不能比较大小。
  • 2 相同类型的结构体才能够进行比较,结构体是否相同不但与属性类型有关,还与属性顺序相关,sn3 与 sn1 就是不同的结构体;
sn3:= struct {
         name string
         age  int
     }{age:11,name:"qq"}
  • 4 如果 struct 的所有成员都可以比较,则该 struct 就可以通过 ==!= 进行比较是否相等,比较时逐个项进行比较,如果每一项都相等,则两个结构体才相等,否则不相等;

那什么是可比较的呢,常见的有 bool、数值型、字符、指针、数组等。

切片、map、函数等是不能比较的。

1 下面这段代码能否通过编译?如果通过,输出什么?

package main

import "fmt"

type MyInt1 int
type MyInt2 = int

func main() {
	var i int = 0
	var i1 MyInt1 = i
	var i2 MyInt2 = i
	fmt.Println(i1, i2)
}

参考答案及解析:
编译不通过,cannot use i (type int) as type MyInt1 in assignment

这道题考的是类型别名与类型定义的区别。

第 5 行代码是基于类型 int 创建了新类型 MyInt1,
第 6 行代码是创建了 int 的类型别名 MyInt2,

注意类型别名的定义时 =

所以,第 10 行代码相当于是将 int 类型的变量赋值给 MyInt1 类型的变量,Go 是强类型语言,编译当然不通过;

而 MyInt2 只是 int 的别名,本质上还是 int,可以赋值。

第 10 行代码的赋值可以使用强制类型转化 var i1 MyInt1 = MyInt1(i)

package main

import "fmt"

type MyInt1 int
type MyInt2 = int

func main() {
	var i int = 0
	var i1 MyInt1 = MyInt1(i)
	var i2 MyInt2 = i
	fmt.Println(i1, i2)
}

2 关于字符串连接,下面语法正确的是?

A. str := ‘abc’ +123’
B. str := “abc” +123”
str :=123+ “abc”
D. fmt.Sprintf(“abc%d”, 123)

参考答案及解析:BD。

知识点:字符串连接。

除了以上两种连接方式,还有 strings.Join()buffer.WriteString() 等。

3 下面这段代码能否编译通过?如果可以,输出什么?

package main

import "fmt"

const (
	x = iota
	_
	y
	z = "zz"
	k
	p = iota
)

func main() {
	fmt.Println(x, y, z, k, p)
	// 0 2 zz zz 5
}

参考答案及解析:
编译通过,输出:0 2 zz zz 5。
知识点:iota 的使用。

4 下面赋值正确的是?

A. var x = nil
B. var x interface{} = nil
C. var x string = nil
D. var x error = nil

参考答案及解析:BD。

知识点:nil 值。

nil 只能赋值给指针、chan、func、interface、map 或 slice 类型的变量。

强调下 D 选项的 error 类型,它是一种内置接口类型,看下方贴出的源码就知道,所以 D 是对的。

5 关于 init 函数,下面说法正确的是?

A. 一个包中,可以包含多个 init 函数;
B. 程序编译时,先执行依赖包的 init 函数,再执行 main 包内的 init 函数;
C. main 包中,不能有 init 函数;
D. init 函数可以被其他函数调用;

参考答案及解析:AB。

关于 init() 函数有几个需要注意的地方:

1 init() 函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等;

2 一个包可以出线多个 init() 函数,一个源文件也可以包含多个 init() 函数;

3 同一个包中多个 init() 函数的执行顺序没有明确定义,但是不同包的 init 函数是根据包导入的依赖关系决定的(看下图);

4 init() 函数在代码中不能被显示调用、不能被引用(赋值给函数变量),否则出现编译错误;

5 一个包被引用多次,如 A import B,C import B,A import C,B 被引用多次,但 B 包只会初始化一次;

6 引入包,不可出现死循坏。
即 A import B,B import A,这种情况编译失败;

在这里插入图片描述

6 下面这段代码输出什么以及原因?

package main

import "fmt"

func hello() []string {
	return nil
}

func main() {
	h := hello
	if h == nil {
		fmt.Println("nil")
	} else {
		fmt.Println("not nil")
	}
}

A. nil

B. not nil

C. compilation error

答案及解析:B。
这道题目里面,是将 hello() 赋值给变量 h,而不是函数的返回值,所以输出 not nil。

package main

import "fmt"

func hello() []string {
	return nil
}

func main() {
	h := hello
	if h() == nil {
		fmt.Println("nil")
	} else {
		fmt.Println("not nil")
	}
}

输出:nil

7 下面这段代码能否编译通过?如果可以,输出什么?

package main

func GetValue() int {
	return 1
}

func main() {
	i := GetValue()
	switch i.(type) {
	case int:
		println("int")
	case string:
		println("string")
	case interface{}:
		println("interface")
	default:
		println("unknown")
	}
}

参考答案及解析:编译失败。
考点:类型选择,类型选择的语法形如:i.(type),其中 i 是接口,type 是固定关键字,需要注意的是, 只有接口类型才可以使用类型选择。

package main

func iType(x interface{}) {
	switch x.(type) {
	case int:
		println("int")
		println(fmt.Sprintf("int %#v", x))
		// int 1
	case string:
		println("string")
	case interface{}:
		println("interface")
	default:
		println("unknown")
	}
}

func GetValue() int {
	return 1
}

func main() {
	i := GetValue()
	iType(i)
}

8 关于channel,下面语法正确的是?

A. var ch chan int

B. ch := make(chan int)

C. <- ch

D. ch <-

参考答案及解析:ABC。
A、B都是声明 channel;
C 读取 channel;
写 channel 是必须带上值,所以 D 错误。

9 下面这段代码输出什么?

package main

import "fmt"

type person struct {
	name string
}

func main() {
	var m map[person]int
	p := person{"mike"}
	fmt.Println(m[p])
}

A.0

B.1

C.Compilation error

E:\TEXT\test_go\one\test>go run main.go
0

E:\TEXT\test_go\one\test>

参考答案及解析:A。

打印一个 map 中不存在的值时,返回元素类型的零值。

这个例子中,m 的类型是 map[person]int,因为 m 中不存在 p,所以打印 int 类型的零值,即 0。

10 下面这段代码输出什么?

package main

import "fmt"

func hello(num ...int) {
	num[0] = 18
}

func main() {
	i := []int{5, 6, 7}
	hello(i...)
	fmt.Println(i[0])
}

A.18

B.5

C.Compilation error

参考答案及解析:18。
知识点:可变函数。

11 关于 cap() 函数的适用类型,下面说法正确的是?

A. array

B. slice

C. map

D. channel

参考答案及解析:ABD。

知识点:cap(),cap() 函数不适用 map。

12 下面这段代码输出什么?

package main

import "fmt"

func main() {
	var i interface{}
	if i == nil {
		fmt.Println("nil") // nil
		return
	}
	fmt.Println("not nil")
}

A. nil

B. not nil

C. compilation error

参考答案及解析:A。

当且仅当接口的动态值和动态类型都为 nil 时,接口类型值才为 nil。

13 下面这段代码输出什么?

package main

import "fmt"

func main() {
	s := make(map[string]int)
	delete(s, "h")
	fmt.Println(s["h"]) // 0
}

A. runtime panic

B. 0

C. compilation error

参考答案及解析:B。

删除 map 不存在的键值对时,不会报错,相当于没有任何作用;

获取不存在的减值对时,返回值类型对应的零值,所以返回 0。

14 下面属于关键字的是?

A.func

B.struct

C.class

D.defer

参考答案及解析:ABD。

知识点:Go 语言的关键字。Go 语言有 25 个关键字,看下图:

在这里插入图片描述

15 下面这段代码输出什么?

package main

import "fmt"

func main() {
	i := -5
	j := +5
	fmt.Printf("%+d %+d", i, j)
	// -5 +5
}

A. -5 +5

B. +5 +5

C. 0 0

参考答案及解析:A。

%d 表示输出十进制数字,+表示输出数值的符号。

这里不表示取反。

16 下面这段代码输出什么?

package main

import "fmt"

type People struct{}

func (p *People) ShowA() {
	fmt.Println("showA")
	p.ShowB()
}

func (p *People) ShowB() {
	fmt.Println("showB")
}

type Teacher struct {
	People
}

func (t *Teacher) ShowB() {
	fmt.Println("teacher showB")
}

func main() {
	t := Teacher{}
	t.ShowB()
	// teacher showB
}

参考答案及解析:teacher showB。

知识点:结构体嵌套。

在嵌套结构体中,People 称为内部类型,Teacher 称为外部类型;

通过嵌套,内部类型的属性、方法,可以为外部类型所有,就好像是外部类型自己的一样。

此外,外部类型还可以定义自己的属性和方法,甚至可以定义与内部相同的方法,这样内部类型的方法就会被“屏蔽”。

这个例子中的 ShowB() 就是同名方法。

17 定义一个包内全局字符串变量,下面语法正确的是?

A. var str string

B. str := “”

C. str = “”

D. var str = “”

参考答案及解析:AD。

B 只支持局部变量声明;
C 是赋值,str 必须在这之前已经声明;

18 下面这段代码输出什么?

package main

import "fmt"

func hello(i int) {
	fmt.Println(i)
}

func main() {
	i := 5
	defer hello(i)
	i = i + 10
}

参考答案及解析:5。

这个例子中,hello() 函数的参数在执行 defer 语句的时候会保存一份副本,在实际调用 hello() 函数时用,所以是 5。

19 下面代码输出什么?

func main() {
    str := "hello"
    str[0] = 'x'
    fmt.Println(str)
}

A. hello

B. xello

C. compilation error

参考代码及解析:C。
知识点:常量,Go 语言中的字符串是只读的。

20 下面代码输出什么?

func incr(p *int) int {
    *p++
    return *p
}

func main() {
    p :=1
    incr(&p)
    fmt.Println(p)
}

A. 1

B. 2

C. 3

参考答案及解析:B。

知识点:指针,incr() 函数里的 p 是 *int 类型的指针,指向的是 main() 函数的变量 p 的地址。

第 2 行代码是将该地址的值执行一个自增操作,incr() 返回自增后的结果。

21 下面代码下划线处可以填入哪个选项?

package main

import "fmt"

func main() {
	var s1 []int
	var s2 = []int{}
	
	if __ == nil {
		fmt.Println("yes nil")
	} else {
		fmt.Println("no nil")
	}
}

A. s1

B. s2

C. s1、s2 都可以

参考答案及解析:A。

知识点:nil 切片和空切片。nil 切片和 nil 相等,一般用来表示一个不存在的切片;空切片和 nil 不相等,表示一个空的集合。

22 下面这段代码输出什么?

func main() {  
    i := 65
    fmt.Println(string(i))
}

A. A

B. 65

C. compilation error

参考答案及解析:A。

UTF-8 编码中,十进制数字 65 对应的符号是 A。

23 下面这段代码输出什么?

package main

import "fmt"

type A interface {
	ShowA() int
}

type B interface {
	ShowB() int
}

type Work struct {
	i int
}

func (w Work) ShowA() int {
	return w.i + 10
}

func (w Work) ShowB() int {
	return w.i + 20
}

func main() {
	c := Work{3}
	var a A = c
	var b B = c
	fmt.Println(a.ShowA())
	fmt.Println(b.ShowB())
}

参考答案及解析:13 23。

知识点:接口。

一种类型实现多个接口,结构体 Work 分别实现了接口 A、B,所以接口变量 a、b 调用各自的方法 ShowA() 和 ShowB(),输出 13、23。

24 切片 a、b、c 的长度和容量分别是多少?

package main

import "fmt"

func main() {
	s := [3]int{1, 2, 3}
	a := s[:0]
	fmt.Println(a) //[]
	b := s[:2]
	fmt.Println(b) // [1 2]
	c := s[1:2:cap(s)]
	fmt.Println(c) // [2]
}

25 下面代码中 A B 两处应该怎么修改才能顺利编译?

package main

import "fmt"

func main() {
	var m map[string]int //A
	m["a"] = 1
	if v := m["b"]; v != nil { //B
		fmt.Println(v)
	}
}

参考答案及解析:

package main

import "fmt"

func main() {
	m := make(map[string]int)
	m["a"] = 1
	if v, ok := m["b"]; ok {
		fmt.Println(v)
	}
}

在 A 处只声明了map m ,并没有分配内存空间,不能直接赋值,需要使用 make(),都提倡使用 make() 或者字面量的方式直接初始化 map。

B 处,v,k := m[“b”] 当 key 为 b 的元素不存在的时候,v 会返回值类型对应的零值,k 返回 false。

26 下面代码中,x 已声明,y 没有声明,判断每条语句的对错?

1. x, _ := f()
2. x, _ = f()
3. x, y := f()
4. x, y = f()

参考答案及解析:错、对、对、错。

知识点:变量的声明。
1.错,x 已经声明,不能使用 :=

2.对;
3.对,当多值赋值时,:= 左边的变量无论声明与否都可以;
4.错,y 没有声明。

27 下面代码输出什么?

package main

import "fmt"

func increaseA() int {
	var i int
	defer func() {
		i++
	}()
	return i
}

func increaseB() (r int) {
	defer func() {
		r++
	}()
	return r
}

func main() {
	fmt.Println(increaseA(), "increaseA")
	fmt.Println(increaseB(), "increaseB")
}

/*
PS E:\TEXT\test_go\one\test> go run .\main.go
0 increaseA
1 increaseB
PS E:\TEXT\test_go\one\test>
*/

A. 1 1

B. 0 1

C. 1 0

D. 0 0

参考答案及解析:B。

知识点:defer、返回值。

注意一下,increaseA() 的返回参数是匿名,increaseB() 是具名。

28 类型断言

package main

import "fmt"

type A interface {
	ShowA() int
}

type B interface {
	ShowB() int
}

type Work struct {
	i int
}

func (w Work) ShowA() int {
	return w.i + 10
}

func (w Work) ShowB() int {
	return w.i + 20
}

func main() {
	var a A = Work{3}
	s := a.(Work)
	fmt.Println(s.ShowA())
	fmt.Println(s.ShowB())
}

A. 13 23

B. compilation error

参考答案及解析:A。

知识点:类型断言。

29 匿名返回

package main

import "fmt"

func f1() (r int) {
	defer func() {
		r++
	}()
	return 0
}

func f2() (r int) {
	t := 5
	defer func() {
		t = t + 5
	}()
	return t
}

func f3() (r int) {
	defer func(r int) {
		r = r + 5
	}(r)
	return 1
}

func main() {
	fmt.Println(f1(), "f1")
	fmt.Println(f2(), "f2")
	fmt.Println(f3(), "f3")
}
PS E:\TEXT\test_go\one\test> go run .\main.go
1 f1
5 f2
1 f3
PS E:\TEXT\test_go\one\test>