Go-牛客刷题专项练习-day1

Golang专项练习

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 typeconcrete type.简单俩说 static type 是你再编码时候看见的类型(如intstring), 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循环支持continuebreak来控制循环,但是它提供了一个更高级的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下面并没有元文件记录引用包的版本信息,这个引用外部包升级产生很大的问题,无法评估升级带来的风险;

Go Vendor介绍

29. 关于函数返回值的错误设计,下面的说法正确的是()

A: 如果失败原因只有一个,则返回bool
B: 如果失败原因超过一个,则返回error
C: 如果没有失败原因,则不返回 boolerror
D: 如果重试几次可以避免失败,则不要立即返回boolerror

答案是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。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值