更多的结构体
在Golang中,数组的长度是不能改变的,为了更灵活地适配更多需求,Golang设计了一种名为Slice的数据结构,用于实现对动态数组的抽象:
1 2 3 4 complex_2 := Complex{1 , 3 } complex_3 := Complex{1 , 4 } complex_list := [2 ]Complex{complex_2, complex_3} complex_slice := []Complex{complex_2, complex_3}
接口
我很久之前就听说过Golang有接口,当时还很意外——一个非OOP的语言,为什么会有接口?稍微学习了一点,写出了一点代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package mainimport ( "fmt" ) type Lamp interface { On() string Off() string GetStatus() bool } type TableLamp struct { IsOn bool Information string } func (tablelamp *TableLamp) On() string { tablelamp.IsOn = true return tablelamp.Information + " ON" } func (tableLamp *TableLamp) Off() string { tableLamp.IsOn = false return tableLamp.Information + " OFF" } func (tableLamp TableLamp) GetStatus() bool { return tableLamp.IsOn } func main () { tableLamp := TableLamp{ Information: "Table Lamp Switches" , IsOn: false , } fmt.Println(tableLamp.On()) fmt.Println(tableLamp.GetStatus()) fmt.Println(tableLamp.Off()) fmt.Println(tableLamp.GetStatus()) }
Golang的所谓接口,在我看来就是模仿OOP的设计,其含义为规定了一类范畴的结构体,必须要实现的方法。
不过,很有意思的是,Golang引入了一个很神奇的东西——空接口,这个东西可以承载任意类型的参数:
1 2 3 4 5 6 7 func main () { var i interface {} i = 1 fmt.Println(i) i = "This is a string" fmt.Println(i) }
对我来说,空接口干的事其实是实现了一个类型声明——any。在Golang1.18版本中,也的确引入了这个关键字,官方解释是,any和空接口是一样的,所以这两种写法是一样的:
1 2 3 4 5 6 7 func main () { var i any i = 1 fmt.Println(i) i = "This is a string" fmt.Println(i) }
泛型
泛型是我认为最有趣的一个功能,一个代码实例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport ( "fmt" ) type EnableString interface { ToString() string } type BasicString struct { str string } func (bs BasicString) ToString() string { return bs.str } func CompareStringLength [T EnableString ](a, b T) bool { return len (a.ToString()) > len (b.ToString()) } func main () { bs1 := BasicString{str: "Hello" } bs2 := BasicString{str: "Exactly" } fmt.Println(CompareStringLength(bs1, bs2)) }
Golang中的泛型是极其灵活的,可以通过实现一个接口自定义泛型的约束,只要一个结构体实现了这个约束,就可以应用泛型函数进行处理。通过泛型我们可以实现很多有趣的功能,比如去重功能:
1 2 3 4 5 6 7 8 9 10 11 func FindDuplicatedElement [T comparable ](a []T) []T { result := []T{} for index, element := range a { for _, element2 := range a[index+1 :] { if element == element2 { result = append (result, element) } } } return result }
学习到这里,我已经很喜欢Golang这门语言了。
并发
众所周知,b站的一部分开发是依靠Golang完成的,那么我先入为主地认为,Golang的在处理并发场景下也是不错的。在Golang里,创建一个新的线程其实非常简单,一个代码的例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "fmt" "time" ) func task1 () { for i := 0 ; i < 3 ; i++ { fmt.Println("This is task1 executing..." ) } time.Sleep(100 * time.Millisecond) } func task2 () { for i := 0 ; i < 3 ; i++ { fmt.Println("This is task2 executing..." ) } time.Sleep(100 * time.Millisecond) } func main () { go task1() go task2() for i := 0 ; i < 3 ; i++ { fmt.Println("This is main executing..." ) time.Sleep(100 * time.Millisecond) } }
只需要go关键字,就可以创建一个运行某个函数的新线程。至于通道等机制,我打算通过写实际的项目再来学习。
继承
Golang虽然不是一个OOP语言,但是也在一定程度上实现了继承的功能,只不过,和OOP语言相比,Golang的继承更像是一个包含关系,而非严格意义上的派生关系,比如一个来自菜鸟教程的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package mainimport "fmt" type Speaker interface { Speak() } type Animal struct { Name string } func (a *Animal) Speak() { fmt.Println(a.Name, "says hello!" ) } type Dog struct { Animal Breed string } func main () { var speaker Speaker dog := Dog{ Animal: Animal{Name: "Buddy" }, Breed: "Golden Retriever" , } speaker = &dog speaker.Speak() }
子结构体通过包含父结构体,实现了所谓的继承操作,除此之外,方法的继承也是在这个包含关系基础上的。
总结
通过加起来一天的上手,Golang具体是怎么一门语言已经十分清晰了,下一步我打算用Golang重写之前用C或者Java等语言实现的一些玩具项目,进一步熟悉Golang本身。
目前为止,我非常喜欢Golang这门语言,因为我所接触的场景大多不是性能优先的场景,所以不需要极致的高性能。Golang或许是我的最佳选择。