Golang 防掉坑笔记

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 面试每天一篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值