Golang专项练习
- 1. golang虽然没有显式的提供继承语法,但是通过匿名组合实现了继承。
- 2. 下面代码中两个斜点之间的代码,比如json:"x",作用是X字段在从结构体实例编码到JSON数据格式的时候,使用x作为名字,这可以看作是一种重命名的方式(如下图),这一说法是否正确。
- 3. Golang支持反射,反射最常见的使用场景是做对象的序列化,这一说法是否正确。
- 4. 关于函数声明下面错误的是?
- 5. 下面代码中的指针p是野指针,因为返回的栈内存在函数结束时会被释放()
- 6. 下面的程序的运行结果是()
- 7. golang支持goto语句,这一说法是否正确。
- 8. 描述下面for循环的输出
- 9. channel本身必然是同事支持读写的, 所以不存在单项channel。这一说法是否正确
- 10. 对变量x的取反操作是~x,这一说法是否正确
- 11. 关于map,下面说法正确的是()
- 12. 同级文件的包名不允许有多个。这一说法是否正确
- 13. 下面关于文件操作的代码可能触发异常,这一说法是否正确。
- 14. 关于无缓冲和有冲突的channel,下面的说法正确的是()
- 15. 错误是业务过程的一部分,而异常不是,这一说法是否正确
- 16. value是整形变量,下面if表达式符合编码规范的是()
- 17. 关于异常的触发,下面说法正确的是()
- 18. 关于局部变量的初始化,下面正确的使用方式是()
- 19. 关于goconvey,下面说法正确的是()
- 20. 关于beego框架,下面说法正确的是()
- 21. 关于循环语句,下面说法正确的有()
- 22. 关于main函数(可执行程序的执行起点),下面的说法正确的是()
- 23. golang中的指针运算包括()
- 24. golang中没有隐藏的this指针,这句话的含义是()
- 25. 关于结构,下面说法正确的是()
- 26. 关于布尔变量b的赋值,下面错误的用法是()
- 27. 对于局部变量整型切片x的赋值,下面定义正确的是()
- 28. 关于 go vendor,下面说法正确的是()
- 29. 关于函数返回值的错误设计,下面的说法正确的是()
- 30. 关于GetPodAction定义,下面赋值正确的是()
1. golang虽然没有显式的提供继承语法,但是通过匿名组合实现了继承。
这个说法是正确的,我们可以通过在另外一个struct内包含另一个struct,这样就可以通过匿名组合实现了继承。
2. 下面代码中两个斜点之间的代码,比如json:“x”,作用是X字段在从结构体实例编码到JSON数据格式的时候,使用x作为名字,这可以看作是一种重命名的方式(如下图),这一说法是否正确。
type Position struct {
X int `json:"x"`
Y int `json:"y"`
Z int `json:"z"`
}
这个说法是正确的,这个可以参考Go语言Json格式化
3. Golang支持反射,反射最常见的使用场景是做对象的序列化,这一说法是否正确。
参考文章Golang的反射reflect深入理解和示例
这个说法是正确的,比如下面的使用场景。
编程语言中反射的概念
在计算机科学领域,反射指一类应用,他们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和检测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
每种语言的反射模型都不同,并且有些语言根本不支持反射。Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用。
多插一句,Golang的gRPC也是通过反射实现的。
interface 和 反射
再讲反射之前,先看看Golang关于类型设计的一些原则:
- 变量包括(type, value)两部分
- 理解这一点就知道为什么 nil != nil了
- type 包括
static type
和concrete type
.简单俩说static type
是你再编码时候看见的类型(如int
、string
),concrete type
是runtime系统看见的类型 - 类型断言能都成功,取决于变量的
concrete type
, 而不是static type
, 因此,一个reader
变量如果它的concrete type
也实现了write
方法的话,它可以被类型断言为writer
.
接下来要讲的反射,就是建立在类型之上的,Golang的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是static type),在创建变量的时候就已经确定,反射主要与Golang的interface类型相关(它的type是concrete type),只有interface类型才有反射一说。
4. 关于函数声明下面错误的是?
A. func f(a, b int) (value int, err error)
B. func f(a int, b int) (value int, err error)
C. func f(a, b int) (value int, error)
D. func f(a int, b int) (int, int, error)
正确答案选择C
5. 下面代码中的指针p是野指针,因为返回的栈内存在函数结束时会被释放()
type TimesMathcher struct {
base int
}
func NewTimesMatcher(base int) *TimesMatcher {
return &TimesMatcher{base:base}
}
func main() {
p := NewTimesMatcher(3)
...
}
这个说法是错误的,这里go当中函数返回的时候,这个地址并不会被释放,所以是可以被访问到的。
6. 下面的程序的运行结果是()
func main() {
if (true) {
defer fmt.Printf("1")
} else {
defer fmt.Printf("2")
}
fmt.Printf("3")
}
程序输出结果为 31
, 因为defer
是延迟执行,在函数运行完了才开始执行一系列defer。
7. golang支持goto语句,这一说法是否正确。
这句话是对的,具体参考标签与goto
for、switch 或 select 语句都可以配合标签(label)形式的标识符使用,即某一行第一个以冒号(:)结尾的单词(gofmt 会将后续代码自动移至下一行)。
package main
import "fmt"
func main() {
LABEL1:
for i := 0; i <= 5; i++ {
for j := 0; j <= 5; j++ {
if j == 4 {
continue LABEL1
}
fmt.Printf("i is: %d, and j is: %d\n", i, j)
}
}
}
本例中,continue 语句指向 LABEL1,当执行到该语句的时候,就会跳转到 LABEL1 标签的位置。
您可以看到当 j4 和 j5 的时候,没有任何输出:标签的作用对象为外部循环,因此 i 会直接变成下一个循环的值,而此时 j 的值就被重设为 0,即它的初始值。如果将 continue 改为 break,则不会只退出内层循环,而是直接退出外层循环了。另外,还可以使用 goto 语句和标签配合使用来模拟循环。
goto的使用示例:
package main
func main() {
i:=0
HERE:
print(i)
i++
if i==5 {
return
}
goto HERE
}
上面的代码会输出 01234。
使用逆向的 goto 会很快导致意大利面条式的代码,所以不应当使用而选择更好的替代方案。
特别注意 使用标签和 goto 语句是不被鼓励的:它们会很快导致非常糟糕的程序设计,而且总有更加可读的替代方案来实现相同的需求。
如果您必须使用 goto,应当只使用正序的标签(标签位于 goto 语句之后),但注意标签和 goto 语句之间不能出现定义新变量的语句,否则会导致编译失败。
// compile error goto2.go:8: goto TARGET jumps over declaration of b at goto2.go:8
package main
import "fmt"
func main() {
a := 1
goto TARGET // compile error
b := 9
TARGET:
b += a
fmt.Printf("a is %v *** b is %v", a, b)
}
8. 描述下面for循环的输出
i := 0
for { //since there are no checks, this is an infinite loop
if i >= 3 { break }
//break out of this for loop when this condition is met
fmt.Println("Value of i is:", i)
i++;
}
fmt.Println("A statement just after for loop.")
输出为
Value of i is:0
Value of i is:1
Value of i is:2
A statement just after for loop.
for i := 0; i < 7; i++ {
if i % 2 == 0 { continue }
fmt.Println("Odd:", i)
}
输出为:
Odd:1
Odd:3
Odd:5
9. channel本身必然是同事支持读写的, 所以不存在单项channel。这一说法是否正确
有单向通道。一般作为函数的参数或者返回值。 单向通道都是由双向通道转换而来,不能自己声明单向通道,没有意义。 单向通道作为函数或者方法的参数时,表示该函数或者方法作用域内,只能执行接收或者发送操作。 单向通道作为函数或者方法返回值时,表示调用者得到该通道后只能做单向操作。
如题参看如下的定义:
双向:var value chan int
单向只读:var value <-chan int
单向只写:var value chan<-int
单向一般用于参数传递和返回值
10. 对变量x的取反操作是~x,这一说法是否正确
这个说法是错误的,按照下列方式取反
^x// Go语言取反方式和C语言不同,Go语言不支持~符号
11. 关于map,下面说法正确的是()
A: map反序列化时json.unmarshal的入参必须为map的地址
B: 在函数调用中传递map,则子函数中对map元素的增加不会导致父函数中map的修改
C: 在函数调用中传递map,则子函数中对map元素的修改不会导致父函数中map的修改
D: 不能使用内置函数delete删除map的元素
答案解析:
反序列化时用这样的方式来调用:
err := json.Unmarshal(b, &book)
B,C:map是引用对象,在子函数中对引用对象进行操作会影响到父函数中引用对象
D:delete(m map[type]type,key type),delete可以根据key删除map中的元素
12. 同级文件的包名不允许有多个。这一说法是否正确
这个说法是正确的。
因为同级目录下,只能有一个包名,但是包名可与该级目录名不一致。
13. 下面关于文件操作的代码可能触发异常,这一说法是否正确。
file, err := os.Open("test.go")
defer file.Close()
if err != nil {
fmt.Println("open file failed:", err)
return
}
这个说法是正确的,会可能引发异常。
defer应该在if后面,如果文件为空,close会崩溃
file, err := os.Open("/null")
defer func() {
err := file.Close()
if err != nil {
fmt.Println("close error: ", err)
} else {
fmt.Println("close no error")
}
}()
if err != nil {
fmt.Println("open error! ", err)
return
}
输出为:
open error! open /null: The system cannot find the file specified.
close error: invalid argument
defer放在前面确实是会报错。
14. 关于无缓冲和有冲突的channel,下面的说法正确的是()
A: 无缓冲的channel是默认的缓冲为1的channel
B: 无缓冲的channel和有缓冲的channel都是同步的
C: 无缓冲的channel和有缓冲的channel都是非同步的
D: 无缓冲的channel是同步的,而有缓冲的channel是非同步的
正确答案选D
无缓冲的channel是同步的,而有缓冲的channel是非同步的
比如
c1:=make(chan int) 无缓冲
c2:=make(chan int, 1) 有缓冲
c1<-1
无缓冲的 不仅仅是 向 c1 通道放 1 而是 一直要有别的携程 <-c1 接手了 这个参数,那么c1<-1才会继续下去,要不然就一直阻塞着
而 c2<-1 则不会阻塞,因为缓冲大小是1 只有当 放第二个值的时候 第一个还没被人拿走,这时候才会阻塞。
打个比喻
无缓冲的 就是一个送信人去你家门口送信 ,你不在家 他不走,你一定要接下信,他才会走。
无缓冲保证信能到你手上
有缓冲的 就是一个送信人去你家仍到你家的信箱 转身就走 ,除非你的信箱满了 他必须等信箱空下来。
有缓冲的 保证 信能进你家的邮箱
15. 错误是业务过程的一部分,而异常不是,这一说法是否正确
这个说法是正确的。
错误指的是可能出现问题的地方出现了问题,比如打开一个文件时失败,这种情况在人们的意料之中 ;而异常指的是不应该出现问题的地方出现了问题,比如引用了空指针,这种情况在人们的意料之外。可见,错误是业务过程的一部分,而异常不是 。
Golang中引入error接口类型作为错误处理的标准模式,如果函数要返回错误,则返回值类型列表中肯定包含error。error处理过程类似于C语言中的错误码,可逐层返回,直到被处理。
Golang中引入两个内置函数panic和recover来触发和终止异常处理流程,同时引入关键字defer来延迟执行defer后面的函数。
一直等到包含defer语句的函数执行完毕时,延迟函数(defer后的函数)才会被执行,而不管包含defer语句的函数是通过return的正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。
当程序运行时,如果遇到引用空指针、下标越界或显式调用panic函数等情况,则先触发panic函数的执行,然后调用延迟函数。调用者继续传递panic,因此该过程一直在调用栈中重复发生:函数停止执行,调用延迟执行函数等。如果一路在延迟函数中没有recover函数的调用,则会到达该携程的起点,该携程结束,然后终止其他所有携程,包括主携程(类似于C语言中的主线程,该携程ID为1)。
错误和异常从Golang机制上讲,就是error和panic的区别。很多其他语言也一样,比如C++/Java,没有error但有errno,没有panic但有throw。
Golang错误和异常是可以互相转换的:
错误转异常,比如程序逻辑上尝试请求某个URL,最多尝试三次,尝试三次的过程中请求失败是错误,尝试完第三次还不成功的话,失败就被提升为异常了。
异常转错误,比如panic触发的异常被recover恢复后,将返回值中error类型的变量进行赋值,以便上层函数继续走错误处理流程。
16. value是整形变量,下面if表达式符合编码规范的是()
A: if value == 0
B: if value
C: if value != 0
D: if !value
题目的答案是AC, 这个题目的重点就在于区分整形和布尔类型。
17. 关于异常的触发,下面说法正确的是()
A: 空指针解析
B: 下标越界
C: 除数为0
D: 调用panic函数
选择的答案为ABCD,我们在碰到解析空指针、下标越界、除数为0、调用panic函数的情况的时候,都会出发异常。
18. 关于局部变量的初始化,下面正确的使用方式是()
A: var i int = 10
B: ar i = 10
C: i := 10
D: i = 10
正确答案选择ABC
A为最完整的写法,指明了变量名,类型,初始值;
B是简写法,没有指定变量类型,不过go提供了类型推断,其会根据初始值推断类型;
C是快速模式,通过":="快速创建一个变量
19. 关于goconvey,下面说法正确的是()
A: goconvey 是一个支持golang的单元测试框架
B: goconvey能够自动监控文件修改并启动测试,并可以将测试结果实时输出到web界面
C: goconvey提供了丰富的断言简化测试用例的编写
D: goconvey无法与go test集成
跟GoConvey相关的可以参考链接: GoConvey初步认识
20. 关于beego框架,下面说法正确的是()
A: beego是一个golang实现的轻量级HTTP框架
B: beego可以通过注释路由、正则路由等多种方式完成url路由注入
C: 可以使用bee new 工具生成空工程,然后使用bee run 命令自动热编译
D: beego 框架只提供了对url路由的处理,而对于MVC框架中的数据库部分未提供框架支持。
21. 关于循环语句,下面说法正确的有()
A: 循环语句既支持for关键字,也支持while和do-while
B: 关键字for的基本使用方法与C/C++中没有任何差异
C: for循环支持continue和break来控制循环,但是它提供了一个更高级的break,可以选择中断哪一个循环
D: for循环不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多个变量
正确答案选择CD
A: go当中不支持while和do-while
B: go当中for循环的使用糅合了C++和Python的特点
高级break:跳出所有循环的参考代码:
package main
import (
"fmt"
)
func main() {
fmt.Println("1")
Exit:
for i := 0; i < 9; i++ {
for j := 0; j < 9; j++ {
if i+j > 15 {
fmt.Print("exit")
break Exit
}
}
}
fmt.Println("3")
}
这里跳出的是外层的i循环对应的循环,而不是跳出的j循环,这个跟C++使用的方式不一样。
等号左边和右边含有多个表达式,就是平行赋值。
由于Go没有逗号表达式,而++和–是语句而不是表达式,如果想在for中执行多个变量,需要使用平行赋值
D选项,for后面的语句中不能有逗号分割的语句,各个语句必须都是平等的,使用分号分割。for后面可以有无数多个分号
22. 关于main函数(可执行程序的执行起点),下面的说法正确的是()
A:main函数不能带参数
B:main函数不能定义返回值
C:main函数所在的包必须为main包
D:main函数中可以使用flag包来获取和解析命令行参数
题目答案选择:ABCD
Main函数和init函数都没有参数和返回值的定义
23. golang中的指针运算包括()
A:可以对指针进行自增或者自减运算
B:可以通过“&”取指针的地址
C:可以对指针进行自增或者自减运算
D:可以对指针进行自增或者自减运算
正确答案选择BC
A: go当中不能针对指针进行自增或者自减的操作。
D: go当中也不能像C当中那样利用下标进行运算。
package main
import ( "fmt" ) //我觉得可以进行指针下标操作啊
func main() {
x,y := 1, 2 var arr = [...]int{5:2} //数组指针 var pf *[6]int = &arr
//指针数组 pfArr := [...]*int{&x,&y}
fmt.Println(pf) //指针下标***作 fmt.Println(pf[1])
fmt.Println(pfArr[0])
}
24. golang中没有隐藏的this指针,这句话的含义是()
A: 方法施加的对象显式传递,没有被隐藏起来
B: golang沿袭了传统面向对象编程中的诸多概念,比如继承、虚函数和构造函数
C: golang的面向对象表达更直观,对于面向过程只是换了一种语法形式来表达
D: 方法施加的对象不需要非得是指针,也不用非得叫this
这个题目的答案是ACD
A,方法施加的对象显式传递,指的是接收器。需要给结构体增加方法时,需要使用 func (a 结构体名) 方法名(参数列表) (返回值列表) {函数体} 这种形式,在函数体里面,调用结构体成员的时候使用的就是 a.xxx,用 c 语言的方式来解释,就是将对象作为参数传入了函数,函数调用这个参数从而访问对象的成员,当然这个函数是友联函数,可以访问任意访问权限的成员
B,golang 不存在虚函数
C,这玩意看不懂,函数实现接口那块怎么解释?这也是面向对象?至于简化,这不是很主观的词嘛?怎么可以用在客观题上。。我就觉得不简化,那怎么答案是简化呢?不懂…
D,参考 A,可以传对象,不一定要传对象指针,至于名字,喜欢可以用 this,不喜欢可以看 A,用 a/b/c,随你喜欢,go 推荐用结构体名首字母小写
25. 关于结构,下面说法正确的是()
A: 只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就是等价的,可以相互赋值
B: 如果接口A的方法列表是接口B的方法列表的子集,那么接口B可以赋值给接口A
C: 接口查询是否成功,要在运行期才能够确定
D: 接口赋值是否可行,要在运行期才能够确定
正确答案是:ABC
只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就是等价的,可以相互赋值。 Go语言接口是否可以赋值,是在编译期就确定的。接口的查询是在运行期确定。
接口查询实则是类型断言判断,形式常常如下:
val, ok := interfaceName.(TypeName)
判断是否在编译期就能判断,就看写代码的时候GO编辑器会不会提示错误。
- 在接口类型进行赋值操作时,若类型不匹配,则编辑器就会提示错误,因此接口赋值操作是在编译期确定的
- 而接口类型查询(断言操作),编译器是不会提示错误的,因此需要等到运行期才能确定。
A:拥有相同方法列表,那么两个接口实质上同一个接口
B:A是B的子集,意味着A的方法B中都有,那么A是B的基类,所以A=B是可行的
C:接口是否能够调用成功是需要运行的时候才能知道
D:接口赋值是否可行在编译阶段就可以知道
26. 关于布尔变量b的赋值,下面错误的用法是()
A: b = true
B: b = 1
C: b = bool(1)
D: b = (1 == 2)
正确答案是: BC
这题的目标就是区分bool和整型数据的区别。
bool类型和int类型没法强制转换
27. 对于局部变量整型切片x的赋值,下面定义正确的是()
A.
x := []int {
1, 2, 3,
4, 5, 6,
}
B.
x := []int{
1, 2, 3,
4, 5, 6
}
C.
x := []int {
1, 2, 3,
4, 5, 6}
D.
x := []int{1,2,3,4,5,6,}
正确答案是ACD
go语言编译器会自动在以标识符、数字字面量、字母字面量、字符串字面量、特定的关键字(break、continue、fallthrough和return)、增减操作符(++和–)、或者一个右括号、右方括号和右大括号(即)、]、})结束的非空行的末尾自动加上分号。
对于B选项,6是数字字面量,所以在6的后面会自动加上一个分号,导致编译出错。
对于D选项,gofmt会自动把6后面的“,”去掉,关掉gofmt后测试,也能通过编译,正常运行。
28. 关于 go vendor,下面说法正确的是()
A: 基本思路是将引用的外部包的源代码放在当前工程的vendor目录下面
B: 编译go代码会优先从vendor目录先寻找依赖包
C: 可以指定引用某个特定版本的外部包
D: 有了vendor目录后,打包当前的工程代码到其他机器的$GOPATH/src下都可以通过编译
这个题目的答案是 ABD
go vendor
无法精确的引用外部包进行版本控制,不能指定引用某个特定版本的外部包;只是在开发时,将其拷贝过来,但是一旦外部包升级,vendor下的代码不会跟着升级,
而且vendor下面并没有元文件记录引用包的版本信息,这个引用外部包升级产生很大的问题,无法评估升级带来的风险;
29. 关于函数返回值的错误设计,下面的说法正确的是()
A: 如果失败原因只有一个,则返回bool
B: 如果失败原因超过一个,则返回error
C: 如果没有失败原因,则不返回 bool 或 error
D: 如果重试几次可以避免失败,则不要立即返回bool或error
答案是ABCD
D在Go语言圣经有说到,列举了几种常见错误。其中一种就是偶尔性或者不可预知的错误,重新调用函数可以解决的,不直接报错,而是多重试几次,实在不行再报错。
30. 关于GetPodAction定义,下面赋值正确的是()
type Fragment interface {
Exec(transInfo *TransInfo) error
}
type GetPodAction struct {
}
func (g GetPodAction) Exec(transInfo *TransInfo) error {
...
return nil
}
A: var fragment Fragment = new(GetPodAction)
B: var fragment Fragment = GetPodAction
C: var fragment Fragment = &GetPodAction{}
D: var fragment Fragment = GetPodAction{}
答案是:ACD
结构体或结构体指针的定义可以:使用 new 初始化 A;作为结构体字面量初始化C;混合字面量语法D。