- 程式碼理解及糾錯
- 1、defer和panic執行先後順序
- 2、for迴圈元素副本問題
- 3、slice追加元素問題
- 4、返回值命名問題
- 5、用new初始化內建型別問題
- 6、切片append另外一個切片問題
- 7、全域性變數用:=宣告問題
- 8、結構體比較問題
- 9、iota的使用
- 10、介面型別斷言使用
- 11、不同型別相加問題
- 12、陣列型別比較問題
- 13、map刪除不存在的值和獲取不存在的值
- 14、格式化輸出問題
- 15、結構體優先呼叫外層方法
- 16、defer引數傳遞副本
- 17、字串只讀
- 18、整數強轉字串
- 19、切片長度問題
- 20、閉包引用和匿名函式問題
- 21、錯吧字串和nil比較的問題
- 22、return後的defer無效問題
- 23、切片共享底層陣列,append擴容後生成新的陣列
- 24、map無序問題
- 25、defer巢狀其他函式問題
- 26、指標接收者實現介面,值無法呼叫問題
- 27、介面賦值為nil問題
- 28、go中不同型別不能比較
- 29、迴圈且追加切片
- 30、協程閉包引用問題
- 31、陣列傳遞給for迴圈也是值拷貝
- 32、切片擴容生成新的底層陣列
- 33、通道channal和select機制
- 34、常量定址問題
- 35、協程間排程問題
- 36、nil的map不能直接賦值,nil的切片可以用append增加元素
- 37、常量預設賦值問題
- 38、結構體變數可匯出,json反序列化
- 39、結構體雖然值傳遞,引用欄位仍然可操作底層結構
- 40、函式只能和nil比較,不能同其他函式比較
- 41、recover捕獲panic,函式遇panic就不會往下執行
- 42、WaitGroup的wait和add和done問題
- 43、切片共享底層陣列及擴容問題
- 44、切片拷貝問題
- 45、go的鎖不可重入
- 46、結構體帶有鎖,在賦值的之後會一併賦值當前鎖狀態
程式碼理解及糾錯
1、defer和panic執行先後順序
package main
import (
"fmt"
)
func main() {
defer_call()
}
func defer_call() {
defer func() { fmt.Println("列印前") }()
defer func() { fmt.Println("列印中") }()
defer func() { fmt.Println("列印後") }()
panic("觸發異常")
}
列印後
列印中
列印前
panic: 觸發異常
defer 的執行順序是後進先出。當出現 panic 語句的時候,會先按照 defer 的後進先出的順序執行,最後才會執行panic
2、for迴圈元素副本問題
func main() {
slice := []int{0,1,2,3}
m := make(map[int]*int)
for key,val := range slice {
m[key] = &val
// 正確寫法
// value := val
// m[key] = &value
}
for k,v := range m {
fmt.Println(k,"->",*v)
}
}
0 -> 3
1 -> 3
2 -> 3
3 -> 3
這是新手常會犯的錯誤寫法,for range 迴圈的時候會建立每個元素的副本,而不是元素的引用,所以 m[key] = &val 取的都是變數 val 的地址,所以最後 map 中的所有元素的值都是變數 val 的地址,因為最後 val 被賦值為3,所有輸出都是3
3、slice追加元素問題
// 1.
func main() {
s := make([]int, 5)
s = append(s, 1, 2, 3)
fmt.Println(s)
}
// 2.
func main() {
s := make([]int,0)
s = append(s,1,2,3,4)
fmt.Println(s)
}
兩段程式碼分別輸出:
[0 0 0 0 0 1 2 3]
[1 2 3 4]
append 向 slice 新增元素,第一段程式碼常見的錯誤是 [1 2 3],需要注意。
4、返回值命名問題
// 第二個返回值未命名錯誤
func funcMui(x,y int)(sum int,error){
return x+y,nil
}
在函式有多個返回值時,只要有一個返回值有命名,其他的也必須命名。如果有多個返回值必須加上括號();如果只有一個返回值且命名也必須加上括號()。這裡的第一個返回值有命名 sum,第二個沒有命名,所以錯誤。
5、用new初始化內建型別問題
func main() {
list := new([]int)
list = append(list, 1)
fmt.Println(list)
}
不能通過編譯,new([]int) 之後的 list 是一個 *[]int 型別的指標,不能對指標執行 append 操作。可以使用 make() 初始化之後再用。同樣的,map 和 channel 建議使用 make() 或字面量的方式初始化,不要用 new() 。
6、切片append另外一個切片問題
func main() {
s1 := []int{1, 2, 3}
s2 := []int{4, 5}
s1 = append(s1, s2)
fmt.Println(s1)
}
不能通過編譯。append() 的第二個引數不能直接使用 slice,需使用 … 操作符,將一個切片追加到另一個切片上:append(s1,s2…)。或者直接跟上元素,形如:append(s1,1,2,3)。
7、全域性變數用:=宣告問題
var(
size := 1024
max_size = size*2
)
func main() {
fmt.Println(size,max_size)
}
:=只能在函式內部使用
8、結構體比較問題
func main() {
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")
}
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")
}
}
編譯不通過 invalid operation: sm1 == sm2;
- 結構體只能比較是否相等,但是不能比較大小。
- 相同型別的結構體才能夠進行比較,結構體是否相同不但與屬性型別有關,還與屬性順序相關
- 如果 struct 的所有成員都可以比較,則該 struct 就可以通過 == 或 != 進行比較是否相等,比較時逐個項進行比較,如果每一項都相等,則兩個結構體才相等,否則不相等;
那什麼是可比較的呢,常見的有 bool、數值型、字元、指標、陣列等。像切片、map、函式等是不能比較的。 具體可以參考 Go 說明文件。
9、iota的使用
const (
x = iota
_
y
z = "zz"
k
p = iota
)
func main() {
fmt.Println(x,y,z,k,p)
}
編譯通過,輸出:0 2 zz zz 5。知識點:iota 的使用.
- 每次 const 出現時,都會讓 iota 初始化為0
- iota出現後,下面的變數無初始值,會按照iota自增長
- 下劃線_可以跳過該行iota的增長,iota還是按行增長的,知道遇到不為const和_的其他變數
10、介面型別斷言使用
func GetValue() int {
return 1
}
func main() {
i := GetValue()
switch i.(type) {
case int:
println("int")
case string:
println("string")
case interface{}:
println("interface")
default:
println("unknown")
}
}
編譯失敗。考點:型別選擇,型別選擇的語法形如:i.(type),其中 i 是介面,type 是固定關鍵字,需要注意的是,只有介面型別才可以使用型別選擇。
11、不同型別相加問題
func main() {
a := 5
b := 8.1
fmt.Println(a + b)
}
a 的型別是 int,b 的型別是 float,兩個不同型別的數值不能相加,編譯報錯。可以使用型別強轉來相加
12、陣列型別比較問題
func main() {
a := [2]int{5, 6}
b := [3]int{5, 6}
if a == b {
fmt.Println("equal")
} else {
fmt.Println("not equal")
}
}
Go 中的陣列是值型別,可比較,另外一方面,陣列的長度也是陣列型別的組成部分,所以 a 和 b 是不同的型別,是不能比較的,所以編譯錯誤。
13、map刪除不存在的值和獲取不存在的值
func main() {
s := make(map[string]int)
delete(s, "h")
fmt.Println(s["h"])
}
刪除 map 不存在的鍵值對時,不會報錯,相當於沒有任何作用;獲取不存在的減值對時,返回值型別對應的零值,所以返回 0。
14、格式化輸出問題
func main() {
i := -5
j := +5
fmt.Printf("%+d %+d", i, j)
}
%d表示輸出十進位制數字,+表示輸出數值的符號。
15、結構體優先呼叫外層方法
type People struct{}
func (p *People) ShowA() {
fmt.Println("showA")
p.ShowB()
}
func (p *People) ShowB() {
fmt.Println("showB")
}
type Teacher struct {
People
}
func (t *Teacher) ShowB() {
fmt.Println("teacher showB")
}
func main() {
t := Teacher{}
t.ShowB()
}
外部型別通過巢狀可以繼承內部結構體的方法,外部型別還可以定義自己的屬性和方法,甚至可以定義與內部相同的方法,這樣內部型別的方法就會被“遮蔽”。這個例子中的 ShowB() 就是同名方法。
16、defer引數傳遞副本
func hello(i int) {
fmt.Println(i)
}
func main() {
i := 5
defer hello(i)
i = i + 10
}
這個例子中,hello() 函式的引數在執行defer語句的時候會儲存一份副本,在實際呼叫 hello() 函式時用,所以是輸出5
17、字串只讀
func main() {
str := "hello"
str[0] = 'x'
fmt.Println(str)
}
Go 語言中的字串是隻讀的。所以編譯錯誤compilation error
18、整數強轉字串
func main() {
i := 65
fmt.Println(string(i))
}
UTF-8 編碼中,十進位制數字 65 對應的符號是 A。輸出A
19、切片長度問題
func main() {
s := [3]int{1, 2, 3}
a := s[:0]
b := s[:2]
c := s[1:2:cap(s)]
}
a長度是0,容量是3;
b長度是2,容量是3;
c長度是1,容量是2;cap(s)雖然是3,但是子切片的容量不能大於底層陣列的長度
擷取操作有帶 2 個或者 3 個引數,形如:[i:j] 和 [i:j:k],假設擷取物件的底層陣列長度為 l。在操作符 [i:j] 中,如果 i 省略,預設 0,如果 j 省略,預設底層陣列的長度,擷取得到的切片長度和容量計算方法是 j-i、l-i。操作符 [i:j:k],k 主要是用來限制切片的容量,但是不能大於陣列的長度 l,擷取得到的切片長度和容量計算方法是 j-i、k-i。
20、閉包引用和匿名函式問題
type Person struct {
age int
}
func main() {
person := &Person{28}
// 1.
defer fmt.Println(person.age)
// 2.
defer func(p *Person) {
fmt.Println(p.age)
}(person)
// 3.
defer func() {
fmt.Println(person.age)
}()
person.age = 29
}
1.person.age 此時是將 28 當做 defer 函式的引數,會把 28 快取在棧中,等到最後執行該 defer 語句的時候取出,即輸出 28;
2.defer 快取的是結構體 Person{28} 的地址,最終 Person{28} 的 age 被重新賦值為 29,所以 defer 語句最後執行的時候,依靠快取的地址取出的 age 便是 29,即輸出 29;
3.閉包引用,輸出 29;
又由於 defer 的執行順序為先進後出,即 3 2 1,所以輸出 29 29 28。
21、錯吧字串和nil比較的問題
package main
import (
"fmt"
)
func main() {
var x string = nil // 錯誤1
if x == nil { // 錯誤2
x = "default"
}
fmt.Println(x)
}
golang 的字串型別是不能賦值 nil 的,也不能跟 nil 比較。
22、return後的defer無效問題
var a bool = true
func main() {
defer func(){
fmt.Println("1")
}()
if a == true {
fmt.Println("2")
return
}
defer func(){
fmt.Println("3")
}()
}
輸出2 1; defer 關鍵字後面的函式或者方法想要執行必須先註冊,return 之後的 defer 是不能註冊的, 也就不能執行後面的函式或方法
23、切片共享底層陣列,append擴容後生成新的陣列
func main() {
s1 := []int{1, 2, 3}
s2 := s1[1:]
s2[1] = 4
fmt.Println(s1)
s2 = append(s2, 5, 6, 7)
fmt.Println(s1)
}
[1 2 4]
[1 2 4]
1、golang 中切片底層的資料結構是陣列。當使用 s1[1:] 獲得切片 s2,和 s1 共享同一個底層陣列,這會導致 s2[1] = 4 語句影響 s1。
2、append 操作會導致底層陣列擴容,生成新的陣列,因此追加資料後的 s2 不會影響 s1。
24、map無序問題
func main() {
m := map[int]string{0:"zero",1:"one"}
for k,v := range m {
fmt.Println(k,v)
}
}
# 由於map無序,所以輸出結果為
0 zero
1 one
# 或者
1 one
0 zero
25、defer巢狀其他函式問題
func main() {
a := 1
b := 2
defer calc("1", a, calc("10", a, b))
a = 0
defer calc("2", a, calc("20", a, b))
b = 1
}
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4
defer會預先把需要的值存起來,如果該值是一個函式的返回,會先計算。之後再按照defer倒序輸出
26、指標接收者實現介面,值無法呼叫問題
type People interface {
Speak(string) string
}
type Student struct{}
func (stu *Student) Speak(think string) (talk string) {
if think == "speak" {
talk = "speak"
} else {
talk = "hi"
}
return
}
func main() {
var peo People = Student{}
think := "speak"
fmt.Println(peo.Speak(think))
}
編譯失敗;原因是基礎面試題26點,注意事項第二點。如果是值接收者,實體型別的值和指標都可以實現對應的介面;如果是指標接收者,那麼只有型別的指標能夠實現對應的介面
27、介面賦值為nil問題
type People interface {
Show()
}
type Student struct{}
func (stu *Student) Show() {
}
func main() {
var s *Student
if s == nil {
fmt.Println("s is nil")
} else {
fmt.Println("s is not nil")
}
var p People = s
if p == nil {
fmt.Println("p is nil")
} else {
fmt.Println("p is not nil")
}
}
s is nil
p is not nil
這道題看似有點詫異,我們分配給變數 p 的值明明是 nil,然而 p 卻不是 nil。記住一點,當且僅當動態值和動態型別都為 nil 時,介面型別值才為 nil。上面的程式碼,給變數 p 賦值之後,p 的動態值是 nil,但是動態型別卻是 *Student,是一個 nil 指標,所以相等條件不成立
28、go中不同型別不能比較
func main() {
fmt.Println([...]int{1} == [2]int{1})
fmt.Println([]int{1} == []int{1})
}
有兩處錯誤:
1、go 中不同型別是不能比較的,而陣列長度是陣列型別的一部分,所以 […]int{1} 和 [2]int{1} 是兩種不同的型別,不能比較;
2、切片是不能比較的;
29、迴圈且追加切片
func main() {
v := []int{1, 2, 3}
for i := range v {
v = append(v, i)
}
}
fmt.Println(v)
[1 2 3 0 1 2]
不會出現死迴圈,能正常結束。迴圈次數在迴圈開始前就已經確定,迴圈內改變切片的長度,不影響迴圈次數。注意,這點跟java不一樣
public class Test {
public static void main(String[] args) {
List<Integer> lss = new ArrayList<>();
lss.add(1);
lss.add(2);
lss.add(3);
for (Integer integer : lss) {
lss.add(integer);
}
System.out.println(JSON.toJSON(lss));
}
}
# java的這段迴圈,會報錯
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.dtstack.common.test.main(test.java:22)
30、協程閉包引用問題
func main() {
var m = [...]int{1, 2, 3}
for i, v := range m {
go func() {
fmt.Println(i, v)
}()
// 正確寫法
// go func(i,v int) {
// fmt.Println(i, v)
// }(i,v)
}
time.Sleep(time.Second * 3)
}
2 3
2 3
2 3
各個 goroutine 中輸出的 i、v 值都是 for range 迴圈結束後的 i、v 最終值,而不是各個goroutine啟動時的i, v值。可以理解為閉包引用,使用的是上下文環境的值。
31、陣列傳遞給for迴圈也是值拷貝
func main() {
var a = [5]int{1, 2, 3, 4, 5}
var r [5]int
for i, v := range a {
// for i, v := range &a {
if i == 0 {
a[1] = 12
a[2] = 13
}
r[i] = v
}
fmt.Println("r = ", r)
fmt.Println("a = ", a)
}
r = [1 2 3 4 5]
a = [1 12 13 4 5]
可以對陣列的地址進行for迴圈,或者使用切片都可
func main() {
var a = []int{1, 2, 3, 4, 5}
var r [5]int
for i, v := range a {
if i == 0 {
a[1] = 12
a[2] = 13
}
r[i] = v
}
fmt.Println("r = ", r)
fmt.Println("a = ", a)
}
r = [1 12 13 4 5]
a = [1 12 13 4 5]
32、切片擴容生成新的底層陣列
func change(s ...int) {
s = append(s,3)
}
func main() {
slice := make([]int,5,5)
slice[0] = 1
slice[1] = 2
// 觸發擴容
change(slice...)
fmt.Println(slice)
// 新切片長度為2,容量為5。不會觸發擴容,所以會改變底層陣列
change(slice[0:2]...)
fmt.Println(slice)
}
[1 2 0 0 0]
[1 2 3 0 0]
Go 提供的語法糖…,可以將 slice 傳進可變函式,不會建立新的切片。第一次呼叫 change() 時,append() 操作使切片底層陣列發生了擴容,原 slice 的底層陣列不會改變;第二次呼叫change() 函式時,使用了操作符[i,j]獲得一個新的切片,假定為 slice1,它的底層陣列和原切片底層陣列是重合的,不過 slice1 的長度、容量分別是 2、5,所以在 change() 函式中對 slice1 底層陣列的修改會影響到原切片。
33、通道channal和select機制
func main() {
runtime.GOMAXPROCS(1)
int_chan := make(chan int, 1)
string_chan := make(chan string, 1)
int_chan <- 1
string_chan <- "hello"
select {
case value := <-int_chan:
fmt.Println(value)
case value := <-string_chan:
panic(value)
}
}
select 會隨機選擇一個可用通道做收發操作,所以可能觸發異常,也可能不會。
34、常量定址問題
const i = 100
var j = 123
func main() {
fmt.Println(&j, j)
fmt.Println(&i, i)
}
常量不同於變數的在執行期分配記憶體,常量通常會被編譯器在預處理階段直接展開,作為指令資料使用,所以常量無法定址。
35、協程間排程問題
func main() {
ch := make(chan int, 100)
// A
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
}()
// B
go func() {
for {
a, ok := <-ch
if !ok {
fmt.Println("close")
return
}
fmt.Println("a: ", a)
}
}()
close(ch)
fmt.Println("ok")
time.Sleep(time.Second * 10)
}
程式會拋異常。先定義下,第一個協程為 A 協程,第二個協程為 B 協程;當 A 協程還沒起時,主協程已經將 channel 關閉了,當 A 協程往關閉的 channel 傳送資料時會 panic,panic: send on closed channel。
36、nil的map不能直接賦值,nil的切片可以用append增加元素
func main() {
var s []int
s = append(s,1)
var m map[string]int
m["one"] = 1 // 報錯
}
37、常量預設賦值問題
const (
x uint16 = 120
y
s = "abc"
z
)
func main() {
fmt.Printf("%T %v\n", y, y)
fmt.Printf("%T %v\n", z, z)
}
uint16 120
string abc
38、結構體變數可匯出,json反序列化
type People struct {
name string `json:"name"`
}
func main() {
js := `{
"name":"seekload"
}`
var p People
err := json.Unmarshal([]byte(js), &p)
if err != nil {
fmt.Println("err: ", err)
return
}
fmt.Println(p)
}
{}
結構體訪問控制,因為 name 首字母是小寫,導致其他包不能訪問,所以輸出為空結構體。
39、結構體雖然值傳遞,引用欄位仍然可操作底層結構
type T struct {
ls []int
}
func foo(t T) {
t.ls[0] = 100
}
func main() {
var t = T{
ls: []int{1, 2, 3},
}
foo(t)
fmt.Println(t.ls[0])
}
100
呼叫 foo() 函式時雖然是傳值,但 foo() 函式中,欄位 ls 依舊可以看成是指向底層陣列的指標。
40、函式只能和nil比較,不能同其他函式比較
func main() {
var fn1 = func() {}
var fn2 = func() {}
if fn1 != fn2 {
println("fn1 not equal fn2")
}
}
invalid operation: fn1 != fn2 (func can only be compared to nil)
41、recover捕獲panic,函式遇panic就不會往下執行
func f() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("recover:%#v", r)
}
}()
panic(1)
panic(2)
}
func main() {
f()
}
recover:1
當程式 panic 時就不會往下執行,可以使用 recover() 捕獲 panic 的內容。這裡panic(2)得不到執行
42、WaitGroup的wait和add和done問題
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
fmt.Println("1")
wg.Done()
wg.Add(1)
}()
wg.Wait()
}
協程裡面,使用 再次wg.Add(1) 但是沒有其他協程 wg.Done(),導致 panic()。
43、切片共享底層陣列及擴容問題
func main() {
a := [3]int{0, 1, 2}
s := a[1:2] // {1}
s[0] = 11 // 由於共享底層陣列,所以a變為{0, 11, 2} s變為{11}
s = append(s, 12) // a變為{0, 11, 12} s變為{11, 12}
s = append(s, 13) // 超過cap,s發生擴容,不再共享a的底層陣列 s變為{11, 12, 13} a仍為{0, 11, 12}
s[0] = 21 // s變為{21, 12, 13} 底層陣列不共享a,a仍然為{0, 11, 12}
fmt.Println(a)
fmt.Println(s)
}
[0 11 12]
[21 12 13]
44、切片拷貝問題
func main() {
var src, dst []int
src = []int{1, 2, 3}
copy(dst, src)
fmt.Println(dst)
}
[]
copy(dst, src) 函式返回 len(dst)、len(src) 之間的最小值。如果想要將 src 完全拷貝至 dst,必須給 dst 分配足夠的記憶體空間。
可以預分配空間拷貝,或者使用append函式拷貝
func main() {
var src, dst []int
src = []int{1, 2, 3}
dst = make([]int, len(src))
n := copy(dst, src)
fmt.Println(n,dst)
}
func main() {
var src, dst []int
src = []int{1, 2, 3}
dst = append(dst, src...)
fmt.Println("dst:", dst)
}
45、go的鎖不可重入
var mu sync.Mutex
var chain string
func main() {
chain = "main"
A()
fmt.Println(chain)
}
func A() {
mu.Lock()
defer mu.Unlock()
chain = chain + " --> A"
B()
}
func B() {
chain = chain + " --> B"
C()
}
func C() {
mu.Lock()
defer mu.Unlock()
chain = chain + " --> C"
}
fatal error: all goroutines are asleep - deadlock!
會fatal error錯誤。使用 Lock() 加鎖後,不能再繼續對其加鎖,直到利用 Unlock() 解鎖後才能再加鎖。
46、結構體帶有鎖,在賦值的之後會一併賦值當前鎖狀態
type MyMutex struct {
count int
sync.Mutex
}
func main() {
var mu MyMutex
mu.Lock()
var mu1 = mu
mu.count++
mu.Unlock()
mu1.Lock()
mu1.count++
mu1.Unlock()
fmt.Println(mu.count, mu1.count)
}
fatal error: all goroutines are asleep - deadlock!
加鎖後複製變數,會將鎖的狀態也複製,所以 mu1 其實是已經加鎖狀態,再加鎖會死鎖。