Questions - syntax

Kesa...大约 5 分钟

1. =:= 的区别

:= 声明+赋值

= 仅赋值

var foo int
foo = 10
// equal to 
foo := 10

2. 指针的作用

指针用来保存变量的地址。

var x =  5
var p *int = &x
fmt.Printf("x = %d",  *p) // x 可以用 *p 访问
  • *:指针解引用,获取指针指向的值
  • &:获取变量地址

3. Go 允许多个返回值吗

允许

func swap(x, y string) (string, string) {
   return y, x
}

func main() {
   a, b := swap("A", "B")
   fmt.Println(a, b) // B A
}

4. Go 有异常类型吗

没有

只有错误类型error,用于表示异常状态

f, err := os.Open("test.txt")
if err != nil {
    log.Fatal(err)
}

5. 什么是协程(Goroutine)

Goroutine 是与其他函数或方法同时运行的函数或方法。 Goroutines 可以被认为是轻量级的线程。 与线程相比,创建 Goroutine 的开销很小。 Go应用程序同时运行数千个 Goroutine 是非常常见的做法。

6. 如何高效地拼接字符串

Go 语言中,字符串是只读的,也就意味着每次修改操作都会创建一个新的字符串。如果需要拼接多次,应使用 strings.Builder,最小化内存拷贝次数。

var str strings.Builder
for i := 0; i < 1000; i++ {
    str.WriteString("a")
}
fmt.Println(str.String())

7. 什么是 rune 类型

ASCII 码只需要 7 bit 就可以完整地表示,但只能表示英文字母在内的128个字符。

为了表示世界上大部分的文字系统,发明了 Unicode, 它是ASCII的超集,包含世界上书写系统中存在的所有字符,并为每个代码分配一个标准编号(称为Unicode CodePoint),在 Go 语言中称之为 rune,是 int32 类型的别名。

Go 语言中,字符串的底层表示是 byte (8 bit) 序列,而非 rune (32 bit) 序列。例如下面的例子中 使用 UTF-8 编码后各占 3 个 byte,因此 len("Go语言") 等于 8,当然我们也可以将字符串转换为 rune 序列。

fmt.Println(len("Go语言")) // 8
fmt.Println(len([]rune("Go语言"))) // 4

8. 如何判断 map 中是否包含某个 key

if val, ok := dict["foo"]; ok {
    //do something here
}
  • ok:表示 key 是否存在
  • val:key 对应的 value

9. Go 支持默认参数或可选参数吗

Go 语言不支持可选参数(python 支持),也不支持方法重载(java支持)

10. 简述 defer 的特性

  • 执行顺序: 多个 defer 遵循 LIFO(Last In First Out,后进先出)的顺序执行,先定义的 defer 语句后执行
  • 执行时机: defer 在 return 之后执行
  • 参数预计算: 直接使用 defer 调用函数,函数参数会在定义时进行计算并拷贝,导致结果不符合预期; 应将函数放入闭包中执行
func tst() {
	defer fmt.Println("defer 1")
	defer fmt.Println("defer 2")
	fmt.Println("tst return ")
}

func main() {
	tst()
}
// output
tst return
defer 2
defer 1

由上可以看出,执行顺序为 LIFO并且在 return 之后执行

func main() {
	s1 := time.Now()
	defer fmt.Println("Duration-1:", time.Since(s1))

	s2 := time.Now()
	defer func() {
		fmt.Println("Duration-2:", time.Since(s2))
	}()

	time.Sleep(time.Second)
}
// output
Duration-2: 1.0057363s
Duration-1: 0s

defer fmt.Println("Duration-1:", time.Since(s1)):此处在定义时,参数time.Since(s1)已经进行计算完毕,导致计算的时间差为 0

11. defer 能否修改返回值

可以,当函数返回值为:

  1. 具名返回值
  2. 指针类型
func tst() int {
	i := 0
	defer func() {
		i++
	}()

	return i
}

func tst1() (i int) {
	defer func() {
		i++
	}()
	return
}

func tst2() *int {
	i := 0
	defer func() {
		i++
	}()
	return &i
}

func main() {
	fmt.Println(tst())   // 0
	fmt.Println(tst1())  // 1
	fmt.Println(*tst2()) // 1
}

tst中,返回的值已经经过拷贝,在defer中修改局部变量不会影响返回值。

12. 如何交换两个变量

a, b := 1, 2
a, b = b, a
fmt.Println(a, b) // 2, 1

13. 结构体中 tag 的作用

tag 可以作为字段的注解,利用反射可以获取 tag 的值。

框架/工具可以通过反射获取到某个字段定义的属性,采取相应的处理方式。tag 丰富了代码的语义,增强了灵活性。

type Stu struct {
	Name string `json:"stu_name"`
	ID   string `json:"stu_id"`
	Age  string `json:"-"`
}

func main() {
	buf, _ := json.Marshal(Stu{Name: "Alice", ID: "001", Age: "16"})
    // {"stu_name":"Alice","stu_id":"001"}
	fmt.Println(string(buf))
}
  • json:"stu_name":在编码成 JSON 格式时,将字段名改成stu_name
  • json:"-":JSON 编码时,跳过此字段

14. 如何判断切片内容是否相等

go 语言中可以使用反射 reflect.DeepEqual(a, b) 判断 a、b 两个切片是否相等,但是通常不推荐这么做,使用反射非常影响性能。

建议使用遍历的方式(性能比较):

func sliceEqual(a1, a2 []int) bool {
	if len(a1) != len(a2) {
		return false
	}

	// 异或运算
	// 参与比较的切片不能为 nil
	if (a1 == nil) != (a2 == nil) {
		return false
	}

	for i, v := range a1 {
		if a2[i] != v {
			return false
		}
	}

	return true
}

15. 格式化输出中的 %v%+v%#v 区别

func main() {
    type s struct {
       a int
       b int
    }
    sv := s{
       a: 1,
       b: 2,
    }
    fmt.Printf("%v\n", sv)  // {1 2}
    fmt.Printf("%+v\n", sv) // {a:1 b:2}
    fmt.Printf("%#v\n", sv) // main.s{a:1, b:2}
}
  • %v:打印值
  • %+v:打印字段名和值
  • %#v:打印类型名及字段名

16. 如何定义枚举

使用 const 关键字和 iota定义枚举值:

type StuType int32

const (
	Type1 StuType = iota
	Type2
	Type3
	Type4
)

func main() {
	fmt.Println(Type1, Type2, Type3, Type4) // 0, 1, 2, 3
}

iota初始值为 0,后续定义的枚举将会自动加一。

17. 空结构体的作用

空结构体struct{}{},用作占位符,因为其占用内存大小为0。

fmt.Println(unsafe.Sizeof(struct{}{})) // 0

配合 map 类型可以实现 set:

type Set map[string]struct{}

func main() {
	set := make(Set)

	for _, item := range []string{"A", "A", "B", "C"} {
		set[item] = struct{}{}
	}
	fmt.Println(len(set)) // 3
	if _, ok := set["A"]; ok {
		fmt.Println("A exists") // A exists
	}
}

作为 Channel 的元素类型时,可以只用于发送/接收信号:

func main() {
	ch := make(chan struct{}, 1)
	go func() {
		<-ch
		// do something
	}()
	ch <- struct{}{}
	// ...
}

18. new 和 make 区别

  • new 仅用于分配内存,返回指向变量的指针
  • make 用于 slice, map 和 channel 的初始化

19. Golang 如何实现面向对象

  • 封装:同一各包,对象对包内可见;不同的包,仅有导出的对象可见
  • 继承:通过 struct 嵌套实现继承,可实现多重继承
  • 多态:通过接口类型 interface 实现

20. uint 型变量相减会发生什么

对于变量 a, b uint

  • a > b,得到正常值
  • a < b,则发生溢出
func main() {
	var a, b uint
	a = 5
	b = 8
	fmt.Println(b - a) // 3
	fmt.Println(a - b) // 18446744073709551613
}
上次编辑于:
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.2