邻里街坊

这几天看了几集<情满四合院>,这帮老演员演得真不错。也就不对标那些个小鲜肉了,他们除了长得好看,绯闻多。除此之外,没啥可关注的。老演员的一个眼神,一个动作都透着一股劲。这股劲能把人带到戏里面去,能让观众情不自禁的把自己带到那种氛围里面。 好像看的并不是别人家的事情,而是自己家的家长里短。 轮台词,没有华丽的辞藻。轮布景,就是在影棚里面搭出来的景。论演员,刚才说了,没有好看的小鲜肉。 轮特效,没有!论剧情,家长里短,好不出彩。可为啥能打动人,应该是演员的"敬业"精神吧。每个演员都把自己的角色演活了,演的非常生动,非常立体。就好像这些个演员都出自身边环境一样,没有丝毫的距离和陌生感。这种好片子太少了,好演员也太少,因此敬业的精神就显得越发珍贵了。

本节来聊Golang中的结构体(Struct)。Golang的缔造者绝对对C有着很深的感情,Golang从里到外都透着C的感觉。就感觉Golang是C的豪华升级版。结构体,又名Struct。除了在C/C++之外,很少会使用到。这里暂且不谈Struct在C里面的使用方式,只聊在Golang中应该如何使用。

首先来看Struct在Golang中处于什么作用。前文说过Golang中有几种基本数据类型,背不出来,屁股要挨打的哦😔。但凡写过代码的人都清楚,只靠这几种数据类型是无法满足业务逻辑需要的,成熟的语言一定会满足用户自定义数据类型的需要。 在Golang当中,Struct就是为了满足自定义数据类型而诞生的。如果非要类比,那Struct就可以对标成Java里面的Class。比如下面:

  1. type News struct{
  2. Title string
  3. Content string
  4. Date string
  5. }

News就是一个标准的Struct,里面有三个属性。由此,可以推导出Struct的声明语法:

  1. type name struct {
  2. member definition;
  3. member definition;
  4. ...
  5. member definition;
  6. }

name是struct的名称,也可以理解成类名。member可以理解成成员变量,这里要注意,如果首字母大写,则属于public。如果小写,则属于private。而definition则是变量类型,这个类型可以是基本类型也可以是自定义类型,总之是合法数据类型就可以。

一旦声明完成,恭喜你,你拥有了一个独属于你的数据类型。但怎么使用呢?先来看初始化。最简单的方式:

  1. myNews := News{
  2. Title:"xxx",
  3. Content:"xxxx",
  4. Date:"xxxx",
  5. }

这是最简单的初始化方式,其中不是每个变量都需要给值的,想给几个就给几个,想不给,那就直接 myNews := News{}。高兴就好!

初始化之后,就轮到使用了。 怎么用?最简单的方式:myNews.Title就可以了。 如果高兴,也可以仿照Java来个Setter/Getter函数。没关系,不用担心别人说三道四。黑猫白猫,最合适的就是好猫。只要结构清晰,团队成员看起来舒服,学习成本低。爱用哪个就用哪个。

到此,你就可以随心所欲,敞开了用成员变量。甭担心会出错,年轻人写代码不出错还叫年轻人嘛!反正错了在fix呗。如果说看到这里,你就认为Struct说完了,那就找个地方泡杯茶,然后再回来吧。 注意到没有,上面所有的内容都是变量,说白了,还没涉及到成员函数呢。没有函数的Struct,就是一具没有骨骼的肉体,所以下面上骨骼。

和Java不同,Golang的Struct函数不是定义在Struct内部的,而是在外部。例如:

  1. type News struct{
  2. Title string
  3. Content string
  4. Date string
  5. }
  6. func (news *News)GetTitle(){....}

GetTitle就是News的成员函数,除了News的实例能调用。其它人无权调用。这里的写法是不是和以前讲解的函数声明方式不同?也没多什么,就多了一个"归宿"而已。我们告诉编译器,这个函数是属于News的。

在有的团队中(可能是Java转型过来的吧),喜欢声明成这样:

  1. func (this *News)GetTitle(){ this.xxxx .... }

这样使用this会比较顺手。由此看出,news也好,this也罢。只是一个指代当前实例的指针名称而已。 叫啥都行,哪个顺手用哪个。反正只在函数内部生效。

有没有人会提问?这些都是实例方法(Java的说法),有没有静态方法?有呀!,把(this *News)拿掉,函数首字母大写,就是静态方法了。 也就是在此之前,我们一直在写的那些个代码。

Struct在Golang当中,说重要其实也不重要。因为就是一个自定义数据类型而已,无非是添加了一些成员变量,还有成员方法,只要注意首字母大小写就没问题了。 但如果说不重要吧,也有失偏颇,Golang代码中的大部分都是依靠各种各样的Struct来实现的。 因此对于Struct来说,战略上藐视,战术上重视比较合适。

因为Struct也是一种数据类型,所以以前说的各种变量使用场景,这里都适用。比如,将一个Struct当做参数传入函数中:

  1. package main
  2. import "fmt"
  3. type Books struct {
  4. title string
  5. author string
  6. subject string
  7. book_id int
  8. }
  9. func main() {
  10. var Book1 Books /* Declare Book1 of type Book */
  11. var Book2 Books /* Declare Book2 of type Book */
  12. /* book 1 specification */
  13. Book1.title = "Go Programming"
  14. Book1.author = "Mahesh Kumar"
  15. Book1.subject = "Go Programming Tutorial"
  16. Book1.book_id = 6495407
  17. /* book 2 specification */
  18. Book2.title = "Telecom Billing"
  19. Book2.author = "Zara Ali"
  20. Book2.subject = "Telecom Billing Tutorial"
  21. Book2.book_id = 6495700
  22. /* print Book1 info */
  23. printBook(Book1)
  24. /* print Book2 info */
  25. printBook(Book2)
  26. }
  27. func printBook( book Books ) {
  28. fmt.Printf( "Book title : %s\n", book.title);
  29. fmt.Printf( "Book author : %s\n", book.author);
  30. fmt.Printf( "Book subject : %s\n", book.subject);
  31. fmt.Printf( "Book book_id : %d\n", book.book_id);
  32. }

再比如结合分水岭中的指针,对Struct进行指针操作:

  1. package main
  2. import "fmt"
  3. type Books struct {
  4. title string
  5. author string
  6. subject string
  7. book_id int
  8. }
  9. func main() {
  10. var Book1 Books /* Declare Book1 of type Book */
  11. var Book2 Books /* Declare Book2 of type Book */
  12. /* book 1 specification */
  13. Book1.title = "Go Programming"
  14. Book1.author = "Mahesh Kumar"
  15. Book1.subject = "Go Programming Tutorial"
  16. Book1.book_id = 6495407
  17. /* book 2 specification */
  18. Book2.title = "Telecom Billing"
  19. Book2.author = "Zara Ali"
  20. Book2.subject = "Telecom Billing Tutorial"
  21. Book2.book_id = 6495700
  22. /* print Book1 info */
  23. printBook(&Book1)
  24. /* print Book2 info */
  25. printBook(&Book2)
  26. }
  27. func printBook( book *Books ) {
  28. fmt.Printf( "Book title : %s\n", book.title);
  29. fmt.Printf( "Book author : %s\n", book.author);
  30. fmt.Printf( "Book subject : %s\n", book.subject);
  31. fmt.Printf( "Book book_id : %d\n", book.book_id);
  32. }

如果猜的不错,你肯定是鼠标快速划过就完事了。 别走神,里面真有彩蛋。为啥提出这两个例子,是因为具体实践中,这是经常大意犯的错误。先看这两个例子的区别:

  1. func printBook( book Books ) { ... }
  2. func printBook( book *Books ) { ... }

一个是值传递,一个是引用传递。 因此在第一个例子中,如果printBook里面修改了数据,比如:

  1. book.title = "lala"

外面仍然无法感知,因为是值传递。修改的是副本,不是本体。但如果在第二个例子中,修改了数据,外层就会收到影响,因为都是本体。

  1. package main
  2. import "fmt"
  3. type Books struct {
  4. title string
  5. author string
  6. subject string
  7. book_id int
  8. }
  9. func main() {
  10. var Book1 Books /* Declare Book1 of type Book */
  11. var Book2 Books /* Declare Book2 of type Book */
  12. /* book 1 specification */
  13. Book1.title = "Go Programming"
  14. Book1.author = "Mahesh Kumar"
  15. Book1.subject = "Go Programming Tutorial"
  16. Book1.book_id = 6495407
  17. /* book 2 specification */
  18. Book2.title = "Telecom Billing"
  19. Book2.author = "Zara Ali"
  20. Book2.subject = "Telecom Billing Tutorial"
  21. Book2.book_id = 6495700
  22. /* print Book1 info */
  23. printBook(Book1)
  24. fmt.Println(Book1.title)
  25. /* print Book2 info */
  26. printBook(Book2)
  27. }
  28. func printBook(book Books) {
  29. book.title = "lala"
  30. fmt.Printf("Book title : %s\n", book.title);
  31. fmt.Printf("Book author : %s\n", book.author);
  32. fmt.Printf("Book subject : %s\n", book.subject);
  33. fmt.Printf("Book book_id : %d\n", book.book_id);
  34. }

跑一下,看看结果就能看出区别了。

所以就这里需要注意一下,其它关于Struct的坑应该算是没有了吧。 如果有,恩,那就有吧。 坑坑何其多,岂能填的完。如果哪天我开公司,一定要招一个叫"田德湾"的员工,就冲着名字,吉利!