1. 从0开始,组中每定义一个常量,自动递增1
const (
a = iota
b = iota
)
const (
name = "menglu"
c = iota
d = iota
)
func TestExam(t *testing.T) {
fmt.Println(a) // 0
fmt.Println(b) // 1
fmt.Println(c) // 1
fmt.Println(d) // 2
}
2. golang 中的切片底层其实使用的是数组
// 当使用str1[1:] 使,str2 和 str1 底层共享一个数组,这回导致 str2[1] = "new" 语句影响 str1。
// 而 append 会导致底层数组扩容,生成新的数组,因此追加数据后的 str2 不会影响 str1。
// 但是为什么对 str2 复制后影响的确实 str1 的第三个元素呢?
// 这是因为切片 str2 是从数组的第二个元素开始,
// str2 索引为 1 的元素对应的是 str1 索引为 2 的元素。
func TestExam00(t *testing.T) {
str1 := []string{"a", "b", "c"}
str2 := str1[1:]
str2[1] = "new"
fmt.Println(str1) // [a b new]
str2 = append(str2, "z", "x", "y")
fmt.Println(str2) // [b new z x y]
}
3. 指针类型比较的是指针地址,非指针类型比较的是每个属性的值
type Student struct {
Name string
Age int
}
func TestExam01(t *testing.T) {
fmt.Println(&Student{Name: "menglu"} == &Student{Name: "menglu"}) // false
fmt.Println(Student{Name: "menglu"} == Student{Name: "menglu"}) // true
}
4. Mutex 是互斥锁
var mu sync.Mutex
var chain string
func A() {
mu.Lock()
defer mu.Lock()
chain = chain + " --> A"
B()
}
func B() {
chain = chain + " --> B"
C()
}
func C() {
mu.Lock()
defer mu.Lock()
chain = chain + " --> C"
}
// panic
func TestExam02(t *testing.T) {
chain = "main"
A()
fmt.Println(chain)
}
5. RUnlock 撤销单次 RLock 调用,它对于其它同时存在的读取器则没有效果;写锁权限高于读锁,有写锁时优先进行写锁定
var mu1 sync.RWMutex
var count int
// 去掉一个defer
// 或去掉一重读锁
func A1() {
mu1.RLock()
defer mu1.RUnlock()
B1()
}
func B1() {
time.Sleep(5 * time.Second)
C1()
}
func C1() {
mu1.RLock()
defer mu1.RUnlock()
}
// fatal error: all goroutines are asleep - deadlock!
func TestExam03(t *testing.T) {
go A1()
time.Sleep(2 * time.Second)
mu1.Lock()
defer mu1.Unlock()
count++
fmt.Println(count)
}
6. WaitGroup 在调用 Wait 之后是不能再调用 Add 方法的
建议是每调用一次Add(1)方法,再写一个Go程
// panic
func TestExam04(t *testing.T) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
time.Sleep(time.Millisecond)
wg.Done()
wg.Add(1)
}()
wg.Wait()
}
7. 在多核CPU中,因为CPU缓存会导致多个核心中变量值不同步
type Once struct {
m sync.Mutex
done uint32
}
func (o *Once) Do(f func()) {
if o.done == 1 {
return
}
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
o.done = 1
f()
}
}
func TestExam05(t *testing.T) {
l := Once{}
l.Do(func() {
println("A")
})
for i := 0; i < 10; i++ {
l.Do(func() {
j := 0
println(j)
j++
})
time.Sleep(1 * time.Second)
}
}
8. 加锁后复制变量,会将锁的状态也复制
// panic
func TestExam06(t *testing.T) {
var mu MyMutex
mu.Lock()
var mu2 = mu
mu.count++
mu.Unlock()
mu2.Lock() // mu2 其实是已经加锁状态,再加锁会死锁
mu2.count++
mu2.Unlock()
fmt.Println(mu.count, mu2.count)
}
9. channel未初始化,写和读都会阻塞
// 一直打印: #goroutines: 3
func TestExam08(t *testing.T) {
var ch chan int
go func() {
ch = make(chan int, 1) // 被重新赋值,导致写的ch 都阻塞
ch <- 1
}()
go func(ch chan int) {
time.Sleep(time.Second)
println(<-ch)
}(ch)
c := time.Tick(1 * time.Second)
for range c {
fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine())
}
}
10. channel未被初始化,关闭时会报错
func TestExam09(t *testing.T) {
var ch chan int
//ch := make(chan int, 1)
var count int
go func() {
ch <- 1
}()
go func() {
count++
close(ch) // 不应该在此处关闭channel,可能此协程会先运行,导致panic: send on closed channel
}()
//defer close(ch)
println(<-ch)
fmt.Println(count)
}
11. 打印一个 map 中不存在的值时,返回元素类型的零值
type person struct {
name string
}
func TestExamThree04(t *testing.T) {
var m map[person]int
p := person{"mike"}
// 可以这样处理
//if _, ok := m[p]; !ok {
// println("值不存在")
//} else {
// fmt.Println(m[p])
//}
fmt.Println(m[p]) // 0
}
12. 在切片后加上 … 后缀,切片将直接传入函数,不会再创建新的切片
func hello(num ...int) {
num[0] = 18
}
func TestExamThree05(t *testing.T) {
i := []int{5, 6, 7}
hello(i...)
fmt.Println(i[0]) // 18
}
13. for range 循环的时候会创建每个元素的副本,而不是元素的引用
// 所以 m[key] = &val 取的都是变量 val 的地址,
// 所以最后 map 中的所有元素的值都是变量 val 的地址,
// 因为最后 val 被赋值为3,所有输出都是3
func TestExamThree10(t *testing.T) {
slice := []int{0, 1, 2, 3}
m := make(map[int]*int)
for key, val := range slice {
m[key] = &val
}
//3 -> 3
//0 -> 3
//1 -> 3
//2 -> 3
for k, v := range m {
fmt.Println(k, "->", *v)
}
}
14. new() 与 make() 的区别
// new(T) 和 make(T,args) 是 Go 语言内建函数,用来分配内存,但适用的类型不同。
//
// new(T) 会为 T 类型的新值分配已置零的内存空间,并返回地址(指针),即类型为 *T 的值。
// 换句话说就是,返回一个指针,该指针指向新分配的、类型为 T 的零值。
// 适用于值类型,如数组、结构体等。
//
// make(T,args) 返回初始化之后的 T 类型的值,
// 这个值并不是 T 类型的零值,也不是指针 *T,是经过初始化之后的 T 的引用。
// make() 只适用于 slice、map 和 channel.
15. 结构体只能比较是否相等,但是不能比较大小。
// 相同类型的结构体才能够进行比较,结构体是否相同不但与属性类型有关,还与属性顺序相关
//
// 如果 struct 的所有成员都可以比较,则该 struct 就可以通过 == 或 != 进行比较是否相等,
// 比较时逐个项进行比较,如果每一项都相等,则两个结构体才相等,否则不相等;
// 切片、map、函数等是不能比较的
func TestExamThree13(t *testing.T) {
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")
}
// compile failed
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")
}
}
16. Go 中的数组是值类型,可比较
// 另外一方面,数组的长度也是数组类型的组成部分,
// 所以 a 和 b 是不同的类型,是不能比较的
func run3() {
a := [2]int{5, 6}
b := [3]int{5, 3}
// compile failed
if a == b {
fmt.Println("equal")
} else {
fmt.Println("not equal")
}
}
17. 删除 map 不存在的键值对时,不会报错,相当于没有任何作用
func TestExamThree19(t *testing.T) {
s := make(map[string]int)
delete(s, "h")
fmt.Println(s["h"])
}
18. 当且仅当接口的动态值和动态类型都为 nil 时,接口类型值才为 nil
func TestExamThree20(t *testing.T) {
var i interface{}
if i == nil {
fmt.Println("nil")
return
}
fmt.Println("not nil")
}
19. Go 语言中的字符串是只读的
func TestExamThree24(t *testing.T) {
str := "make([] string, 0)"
// compile failed
str[0] = 'x'
fmt.Println(str)
}
20. 可变参数,可以接收任意数目的实参
func add(args ...int) int {
sum := 0
for _, arg := range args {
sum += arg
}
return sum
}
// 可变参数就是将任意数目的参数封装成了一个切片
func TestExamThree26(t *testing.T) {
add([]int{1, 3, 7}...)
}
文章中部分知识点参考至:Go 面试每天一篇