go基础语法50问,来看看你的go基础合格了吗?1
1.使用值为 nil 的 slice、map会发生啥
允许对值为 nil 的 slice 添加元素,但对值为 nil 的 map 添加元素,则会造成运行时 panic。
// map 错误示例
func main() {
var m map[string]int
m["one"] = 1 // error: panic: assignment to entry in nil map
// m := make(map[string]int)// map 的正确声明,分配了实际的内存
}
// slice 正确示例
func main() {
var s []int
s = append(s, 1)
}
2.访问 map 中的 key,需要注意啥
当访问 map 中不存在的 key 时,Go 则会返回元素对应数据类型的零值,比如 nil、’’ 、false 和 0,取值操作总有值返回,故不能通过取出来的值,来判断 key 是不是在 map 中。
检查 key 是否存在可以用 map 直接访问,检查返回的第二个参数即可。
// 错误的 key 检测方式
func main() {
x := map[string]string{
"one": "2", "two": "", "three": "3"}
if v := x["two"];
v == "" {
fmt.Println("key two is no entry") // 键 two 存不存在都会返回的空字符串
}
}
// 正确示例
func main() {
x := map[string]string{
"one": "2", "two": "", "three": "3"}
if _, ok := x["two"];
!ok {
fmt.Println("key two is no entry")
}
}
3.string 类型的值可以修改吗
不能,尝试使用索引遍历字符串,来更新字符串中的个别字符,是不允许的。
string 类型的值是只读的二进制 byte slice,如果真要修改字符串中的字符,将 string 转为 []byte 修改后,再转为 string 即可。
// 修改字符串的错误示例
func main() {
x := "text"
x[0] = "T" // error: cannot assign to x[0]
fmt.Println(x)
}
// 修改示例
func main() {
x := "text"
xBytes := []byte(x)
xBytes[0] = 'T' // 注意此时的 T 是 rune 类型
x = string(xBytes)
fmt.Println(x) // Text
}
4.switch 中如何强制执行下一个 case 代码块
switch 语句中的 case 代码块会默认带上 break,但可以使用 fallthrough 来强制执行下一个 case 代码块。
func main() {
isSpace := func(char byte) bool {
switch char {
case ' ': // 空格符会直接 break,返回 false // 和其他语言不一样
// fallthrough // 返回 true
case '\t':
return true
}
return false
}
fmt.Println(isSpace('\t')) // true
fmt.Println(isSpace(' ')) // false
}
5.你是如何关闭 HTTP 的响应体的
直接在处理 HTTP 响应错误的代码块中,直接关闭非 nil 的响应体;手动调用 defer 来关闭响应体。
// 正确示例
func main() {
resp, err := http.Get("http://www.baidu.com")
// 关闭 resp.Body 的正确姿势
if resp != nil {
defer resp.Body.Close()
}
checkError(err)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
checkError(err)
fmt.Println(string(body))
}
6.你是否主动关闭过http连接,为啥要这样做
有关闭,不关闭会程序可能会消耗完 socket 描述符。有如下2种关闭方式:
直接设置请求变量的 Close 字段值为 true,每次请求结束后就会主动关闭连接。设置 Header 请求头部选项 Connection: close,然后服务器返回的响应头部也会有这个选项,此时 HTTP 标准库会主动断开连接
// 主动关闭连接
func main() {
req, err := http.NewRequest("GET", "http://golang.org", nil)
checkError(err)
req.Close = true
//req.Header.Add("Connection", "close") // 等效的关闭方式
resp, err := http.DefaultClient.Do(req)
if resp != nil {
defer resp.Body.Close()
}
checkError(err)
body, err := ioutil.ReadAll(resp.Body)
checkError(err)
fmt.Println(string(body))
}
- 你可以创建一个自定义配置的 HTTP transport 客户端,用来取消 HTTP 全局的复用连接。
func main() {
tr := http.Transport{
DisableKeepAlives: true}
client := http.Client{
Transport: &
tr}
resp, err := client.Get("https://golang.google.cn/")
if resp != nil {
defer resp.Body.Close()
}
checkError(err)
fmt.Println(resp.StatusCode) // 200
body, err := ioutil.ReadAll(resp.Body)
checkError(err)
fmt.Println(len(string(body)))
}
7.解析 JSON 数据时,默认将数值当做哪种类型
在 encode/decode JSON 数据时,Go 默认会将数值当做 float64 处理。
func main() {
var data = []byte(`{
"status": 200}
`)
var result map[string]interface{
}
if err := json.Unmarshal(data, &
result);
err != nil {
log.Fatalln(err)
}
解析出来的 200 是 float 类型。
8.如何从 panic 中恢复
在一个 defer 延迟执行的函数中调用 recover ,它便能捕捉/中断 panic。
// 错误的 recover 调用示例
func main() {
recover() // 什么都不会捕捉
panic("not good") // 发生 panic,主程序退出
recover() // 不会被执行
println("ok")
}
// 正确的 recover 调用示例
func main() {
defer func() {
fmt.Println("recovered: ", recover())
}
()
panic("not good")
}
9.简短声明的变量需要注意啥
- 简短声明的变量只能在函数内部使用
- struct 的变量字段不能使用 := 来赋值
- 不能用简短声明方式来单独为一个变量重复声明, := 左侧至少有一个新变量,才允许多变量的重复声明
10.range 迭代 map是有序的吗
无序的。Go 的运行时是有意打乱迭代顺序的,所以你得到的迭代结果可能不一致。但也并不总会打乱,得到连续相同的 5 个迭代结果也是可能的。
11.recover的执行时机
无,recover 必须在 defer 函数中运行。recover 捕获的是祖父级调用时的异常,直接调用时无效。
func main() {
recover()
panic(1)
}
直接 defer 调用也是无效。
func main() {
defer recover()
panic(1)
}
defer 调用时多层嵌套依然无效。
func main() {
defer func() {
func() {
recover() }
()
}
()
panic(1)
}
必须在 defer 函数中直接调用才有效。
func main() {
defer func() {
recover()
}
()
panic(1)
}
12.闭包错误引用同一个变量问题怎么处理
在每轮迭代中生成一个局部变量 i 。如果没有 i := i 这行,将会打印同一个变量。
func main() {
for i := 0;
i 5;
i++ {
i := i
defer func() {
println(i)
}
()
}
}
或者是通过函数参数传入 i 。
func main() {
for i := 0;
i 5;
i++ {
defer func(i int) {
println(i)
}
(i)
}
}
13.在循环内部执行defer语句会发生啥
defer 在函数退出时才能执行,在 for 执行 defer 会导致资源延迟释放。
func main() {
for i := 0;
i 5;
i++ {
func() {
f, err := os.Open("/path/to/file")
if err != nil {
log.Fatal(err)
}
defer f.Close()
}
()
}
}
func 是一个局部函数,在局部函数里面执行 defer 将不会有问题。
14.说出一个避免Goroutine泄露的措施
可以通过 context 包来避免内存泄漏。
func main() {
ctx, cancel := context.WithCancel(context.Background())
ch := func(ctx context.Context) -chan int {
ch := make(chan int)
go func() {
for i := 0;
;
i++ {
select {
case - ctx.Done():
return
case ch - i:
}
}
}
()
return ch
}
(ctx)
for v := range ch {
fmt.Println(v)
if v == 5 {
cancel()
break
}
}
}
下面的 for 循环停止取数据时,就用 cancel 函数,让另一个协程停止写数据。如果下面 for 已停止读取数据,上面 for 循环还在写入,就会造成内存泄漏。
15.如何跳出for select 循环
通常在for循环中,使用break可以跳出循环,但是注意在go语言中,for select配合时,break 并不能跳出循环。
func testSelectFor2(chExit chan bool){
EXIT:
for {
select {
case v, ok := -chExit:
if !ok {
fmt.Println("close channel 2", v)
break EXIT//goto EXIT2
}
fmt.Println("ch2 val =", v)
}
}
//EXIT2:
fmt.Println("exit testSelectFor2")
}
16.如何在切片中查找
go中使用 sort.searchXXX 方法,在排序好的切片中查找指定的方法,但是其返回是对应的查找元素不存在时,待插入的位置下标(元素插入在返回下标前)。
可以通过封装如下函数,达到目的。
func IsExist(s []string, t string) (int, bool) {
iIndex := sort.SearchStrings(s, t)
bExist := iIndex!=len(s) &
&
s[iIndex]==t
return iIndex, bExist
}
17.如何初始化带嵌套结构的结构体
go 的哲学是组合优于继承,使用 struct 嵌套即可完成组合,内嵌的结构体属性就像外层结构的属性即可,可以直接调用。
注意初始化外层结构体时,必须指定内嵌结构体名称的结构体初始化,如下看到 s1方式报错,s2 方式正确。
type stPeople struct {
Gender bool
Name string
}
type stStudent struct {
stPeople
Class int
}
//尝试4 嵌套结构的初始化表达式
//var s1 = stStudent{
false, "JimWen", 3}
var s2 = stStudent{
stPeople{
false, "JimWen"}
, 3}
fmt.Println(s2.Gender, s2.Name, s2.Class)18.切片和数组的区别
数组是具有固定长度,且拥有零个或者多个,相同数据类型元素的序列。数组的长度是数组类型的一部分,所以[3]int 和 [4]int 是两种不同的数组类型。数组需要指定大小,不指定也会根据初始化的自动推算出大小,不可改变;数组是值传递。数组是内置类型,是一组同类型数据的集合,它是值类型,通过从0开始的下标索引访问元素值。在初始化后长度是固定的,无法修改其长度。
当作为方法的参数传入时将复制一份数组而不是引用同一指针。数组的长度也是其类型的一部分,通过内置函数len(array)获取其长度。数组定义:
var array [10]int
var array =[5]int{
1,2,3,4,5}
切片表示一个拥有相同类型元素的可变长度的序列。切片是一种轻量级的数据结构,它有三个属性:指针、长度和容量。切片不需要指定大小;切片是地址传递;切片可以通过数组来初始化,也可以通过内置函数make()初始化 。初始化时len=cap,在追加元素时如果容量cap不足时将按len的2倍扩容。切片定义:
var slice []type = make([]type, len)
19.new和make的区别
new 的作用是初始化一个指向类型的指针 (*T) 。new 函数是内建函数,函数定义:func new(Type) *Type。使用 new 函数来分配空间。传递给 new 函数的是一个类型,不是一个值。返回值是指向这个新分配的零值的指针。
make 的作用是为 slice,map 或 chan 初始化并返回引用 (T)。make 函数是内建函数,函数定义:func make(Type, size IntegerType) Type;第一个参数是一个类型,第二个参数是长度;返回值是一个类型。
make(T, args) 函数的目的与 new(T) 不同。它仅仅用于创建 Slice, Map 和 Channel,并且返回类型是 T(不是T*)的一个初始化的(不是零值)的实例。
声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!
若转载请注明出处: go基础语法50问,来看看你的go基础合格了吗?1
本文地址: https://pptw.com/jishu/297189.html
